Compare commits
25 Commits
feat-highl
...
c446012199
Author | SHA1 | Date | |
---|---|---|---|
c446012199
|
|||
3053b83bbf
|
|||
938fe1c715 | |||
457316971c
|
|||
eedfc1ffce | |||
0f346d9ded | |||
a662ecc14b | |||
a254d0123d | |||
22d28e763d | |||
d0a4f4b76b | |||
3126005731 | |||
2b9c374e8c | |||
4df3b98e6d | |||
c293d2f6d7 | |||
132261831b | |||
043376c6d3 | |||
4682dacc79 | |||
383dd41695 | |||
7f3221fde9 | |||
05ea729950 | |||
dcad453eb1 | |||
dec2bc937a | |||
b9c44408ad | |||
e1c5362cf5 | |||
bdcfed5506 |
3
.gitattributes
vendored
3
.gitattributes
vendored
@@ -1,2 +1,5 @@
|
||||
*.png filter=lfs diff=lfs merge=lfs -text
|
||||
*.jpg filter=lfs diff=lfs merge=lfs -text
|
||||
*.jpeg filter=lfs diff=lfs merge=lfs -text
|
||||
*.avif filter=lfs diff=lfs merge=lfs -text
|
||||
*.webp filter=lfs diff=lfs merge=lfs -text
|
||||
|
@@ -7,7 +7,7 @@ jobs:
|
||||
Build-Blog-Image:
|
||||
runs-on: archlinux
|
||||
steps:
|
||||
- uses: https://git.rrricardo.top/actions/checkout@v4
|
||||
- uses: https://mirrors.rrricardo.top/actions/checkout.git@v4
|
||||
name: Check out code
|
||||
with:
|
||||
lfs: true
|
||||
@@ -18,12 +18,16 @@ jobs:
|
||||
- name: Build docker image
|
||||
run: |
|
||||
cd YaeBlog
|
||||
docker build . -t registry.cn-beijing.aliyuncs.com/jackfiled/blog:latest
|
||||
podman build . -t registry.cn-beijing.aliyuncs.com/jackfiled/blog:latest --build-arg COMMIT_ID=$(git rev-parse --short=10 HEAD)
|
||||
- name: Workaround to make sure podman login succeed
|
||||
run: |
|
||||
mkdir /root/.docker
|
||||
- name: Login aliyun docker registry
|
||||
uses: https://git.rrricardo.top/actions/login-action@v3
|
||||
uses: https://mirrors.rrricardo.top/actions/podman-login.git@v1
|
||||
with:
|
||||
registry: registry.cn-beijing.aliyuncs.com
|
||||
username: 初冬的朝阳
|
||||
password: ${{ secrets.ALIYUN_PASSWORD }}
|
||||
auth_file_path: /etc/containers/auth.json
|
||||
- name: Push docker image
|
||||
run: docker push registry.cn-beijing.aliyuncs.com/jackfiled/blog:latest
|
||||
run: podman push registry.cn-beijing.aliyuncs.com/jackfiled/blog:latest
|
||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@@ -484,4 +484,4 @@ $RECYCLE.BIN/
|
||||
*.swp
|
||||
|
||||
# Tailwind auto-generated stylesheet
|
||||
output.css
|
||||
*.g.css
|
||||
|
41
YaeBlog.sln
41
YaeBlog.sln
@@ -1,41 +0,0 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.0.31903.59
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "YaeBlog", "YaeBlog\YaeBlog.csproj", "{20438EFD-8DDE-43AF-92E2-76495C29233C}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".gitea", ".gitea", "{9B5AAA29-37D8-454A-8D8F-3E6B6BCF38E6}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{ADBC3DA8-F65C-4B5D-A97A-DC351F8E6592}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
.gitea\workflows\build.yaml = .gitea\workflows\build.yaml
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{753B998C-1B9E-498F-B949-845CE86C4075}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
.editorconfig = .editorconfig
|
||||
.gitattributes = .gitattributes
|
||||
.gitignore = .gitignore
|
||||
README.md = README.md
|
||||
LICENSE = LICENSE
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{20438EFD-8DDE-43AF-92E2-76495C29233C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{20438EFD-8DDE-43AF-92E2-76495C29233C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{20438EFD-8DDE-43AF-92E2-76495C29233C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{20438EFD-8DDE-43AF-92E2-76495C29233C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
{ADBC3DA8-F65C-4B5D-A97A-DC351F8E6592} = {9B5AAA29-37D8-454A-8D8F-3E6B6BCF38E6}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
14
YaeBlog.slnx
Normal file
14
YaeBlog.slnx
Normal file
@@ -0,0 +1,14 @@
|
||||
<Solution>
|
||||
<Folder Name="/.gitea/" />
|
||||
<Folder Name="/.gitea/workflows/">
|
||||
<File Path=".gitea/workflows/build.yaml" />
|
||||
</Folder>
|
||||
<Folder Name="/Solution Items/">
|
||||
<File Path=".editorconfig" />
|
||||
<File Path=".gitattributes" />
|
||||
<File Path=".gitignore" />
|
||||
<File Path="LICENSE" />
|
||||
<File Path="README.md" />
|
||||
</Folder>
|
||||
<Project Path="YaeBlog/YaeBlog.csproj" />
|
||||
</Solution>
|
@@ -7,6 +7,4 @@ public interface IEssayScanService
|
||||
public Task<BlogContents> ScanContents();
|
||||
|
||||
public Task SaveBlogContent(BlogContent content, bool isDraft = true);
|
||||
|
||||
public Task<ImageScanResult> ScanImages();
|
||||
}
|
||||
|
21
YaeBlog/Commands/Binders/ImageCompressServiceBinder.cs
Normal file
21
YaeBlog/Commands/Binders/ImageCompressServiceBinder.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using System.CommandLine.Binding;
|
||||
using YaeBlog.Abstraction;
|
||||
using YaeBlog.Services;
|
||||
|
||||
namespace YaeBlog.Commands.Binders;
|
||||
|
||||
public sealed class ImageCompressServiceBinder : BinderBase<ImageCompressService>
|
||||
{
|
||||
protected override ImageCompressService GetBoundValue(BindingContext bindingContext)
|
||||
{
|
||||
bindingContext.AddService(provider =>
|
||||
{
|
||||
IEssayScanService essayScanService = provider.GetRequiredService<IEssayScanService>();
|
||||
ILogger<ImageCompressService> logger = provider.GetRequiredService<ILogger<ImageCompressService>>();
|
||||
|
||||
return new ImageCompressService(essayScanService, logger);
|
||||
});
|
||||
|
||||
return bindingContext.GetRequiredService<ImageCompressService>();
|
||||
}
|
||||
}
|
@@ -1,4 +1,6 @@
|
||||
using System.CommandLine;
|
||||
using Microsoft.Extensions.Options;
|
||||
using YaeBlog.Abstraction;
|
||||
using YaeBlog.Commands.Binders;
|
||||
using YaeBlog.Components;
|
||||
using YaeBlog.Extensions;
|
||||
@@ -19,6 +21,7 @@ public sealed class YaeBlogCommand
|
||||
AddNewCommand(_rootCommand);
|
||||
AddPublishCommand(_rootCommand);
|
||||
AddScanCommand(_rootCommand);
|
||||
AddCompressCommand(_rootCommand);
|
||||
}
|
||||
|
||||
public Task<int> RunAsync(string[] args)
|
||||
@@ -94,22 +97,20 @@ public sealed class YaeBlogCommand
|
||||
Argument<string> filenameArgument = new(name: "blog name", description: "The created blog filename.");
|
||||
newCommand.AddArgument(filenameArgument);
|
||||
|
||||
newCommand.SetHandler(async (file, _, _, essayScanService) =>
|
||||
newCommand.SetHandler(async (file, blogOption, _, essayScanService) =>
|
||||
{
|
||||
BlogContents contents = await essayScanService.ScanContents();
|
||||
|
||||
if (contents.Posts.Any(content => content.FileName == file))
|
||||
if (contents.Posts.Any(content => content.BlogName == file))
|
||||
{
|
||||
Console.WriteLine("There exists the same title blog in posts.");
|
||||
return;
|
||||
}
|
||||
|
||||
await essayScanService.SaveBlogContent(new BlogContent
|
||||
{
|
||||
FileName = file,
|
||||
FileContent = string.Empty,
|
||||
Metadata = new MarkdownMetadata { Title = file, Date = DateTime.Now }
|
||||
});
|
||||
await essayScanService.SaveBlogContent(new BlogContent(
|
||||
new FileInfo(Path.Combine(blogOption.Value.Root, "drafts", file + ".md")),
|
||||
new MarkdownMetadata { Title = file, Date = DateTime.Now },
|
||||
string.Empty, true, [], []));
|
||||
|
||||
Console.WriteLine($"Created new blog '{file}.");
|
||||
}, filenameArgument, new BlogOptionsBinder(), new LoggerBinder<EssayScanService>(),
|
||||
@@ -126,15 +127,15 @@ public sealed class YaeBlogCommand
|
||||
BlogContents contents = await essyScanService.ScanContents();
|
||||
|
||||
Console.WriteLine($"All {contents.Posts.Count} Posts:");
|
||||
foreach (BlogContent content in contents.Posts.OrderBy(x => x.FileName))
|
||||
foreach (BlogContent content in contents.Posts.OrderBy(x => x.BlogName))
|
||||
{
|
||||
Console.WriteLine($" - {content.FileName}");
|
||||
Console.WriteLine($" - {content.BlogName}");
|
||||
}
|
||||
|
||||
Console.WriteLine($"All {contents.Drafts.Count} Drafts:");
|
||||
foreach (BlogContent content in contents.Drafts.OrderBy(x => x.FileName))
|
||||
foreach (BlogContent content in contents.Drafts.OrderBy(x => x.BlogName))
|
||||
{
|
||||
Console.WriteLine($" - {content.FileName}");
|
||||
Console.WriteLine($" - {content.BlogName}");
|
||||
}
|
||||
}, new BlogOptionsBinder(), new LoggerBinder<EssayScanService>(), new EssayScanServiceBinder());
|
||||
}
|
||||
@@ -150,32 +151,39 @@ public sealed class YaeBlogCommand
|
||||
|
||||
command.SetHandler(async (_, _, essayScanService, removeOptionValue) =>
|
||||
{
|
||||
ImageScanResult result = await essayScanService.ScanImages();
|
||||
BlogContents contents = await essayScanService.ScanContents();
|
||||
List<BlogImageInfo> unusedImages = (from content in contents
|
||||
from image in content.Images
|
||||
where image is { IsUsed: false }
|
||||
select image).ToList();
|
||||
|
||||
if (result.UnusedImages.Count != 0)
|
||||
if (unusedImages.Count != 0)
|
||||
{
|
||||
Console.WriteLine("Found unused images:");
|
||||
Console.WriteLine("HINT: use '--rm' to remove unused images.");
|
||||
}
|
||||
|
||||
foreach (FileInfo image in result.UnusedImages)
|
||||
foreach (BlogImageInfo image in unusedImages)
|
||||
{
|
||||
Console.WriteLine($" - {image.FullName}");
|
||||
Console.WriteLine($" - {image.File.FullName}");
|
||||
}
|
||||
|
||||
if (removeOptionValue)
|
||||
{
|
||||
foreach (FileInfo image in result.UnusedImages)
|
||||
foreach (BlogImageInfo image in unusedImages)
|
||||
{
|
||||
image.Delete();
|
||||
image.File.Delete();
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine("Used not existed images:");
|
||||
|
||||
foreach (FileInfo image in result.NotFoundImages)
|
||||
foreach (BlogContent content in contents)
|
||||
{
|
||||
Console.WriteLine($" - {image.FullName}");
|
||||
foreach (FileInfo file in content.NotfoundImages)
|
||||
{
|
||||
Console.WriteLine($"- {file.Name} in {content.BlogName}");
|
||||
}
|
||||
}
|
||||
}, new BlogOptionsBinder(), new LoggerBinder<EssayScanService>(), new EssayScanServiceBinder(), removeOption);
|
||||
}
|
||||
@@ -193,7 +201,7 @@ public sealed class YaeBlogCommand
|
||||
BlogContents contents = await essayScanService.ScanContents();
|
||||
|
||||
BlogContent? content = (from blog in contents.Drafts
|
||||
where blog.FileName == filename
|
||||
where blog.BlogName == filename
|
||||
select blog).FirstOrDefault();
|
||||
|
||||
if (content is null)
|
||||
@@ -202,14 +210,17 @@ public sealed class YaeBlogCommand
|
||||
return;
|
||||
}
|
||||
|
||||
// 设置发布的时间
|
||||
content.Metadata.Date = DateTime.Now;
|
||||
|
||||
// 将选中的博客文件复制到posts
|
||||
await essayScanService.SaveBlogContent(content, isDraft: false);
|
||||
|
||||
// 复制图片文件夹
|
||||
DirectoryInfo sourceImageDirectory =
|
||||
new(Path.Combine(blogOptions.Value.Root, "drafts", content.FileName));
|
||||
new(Path.Combine(blogOptions.Value.Root, "drafts", content.BlogName));
|
||||
DirectoryInfo targetImageDirectory =
|
||||
new(Path.Combine(blogOptions.Value.Root, "posts", content.FileName));
|
||||
new(Path.Combine(blogOptions.Value.Root, "posts", content.BlogName));
|
||||
|
||||
if (sourceImageDirectory.Exists)
|
||||
{
|
||||
@@ -223,9 +234,30 @@ public sealed class YaeBlogCommand
|
||||
}
|
||||
|
||||
// 删除原始的文件
|
||||
FileInfo sourceBlogFile = new(Path.Combine(blogOptions.Value.Root, "drafts", content.FileName + ".md"));
|
||||
FileInfo sourceBlogFile = new(Path.Combine(blogOptions.Value.Root, "drafts", content.BlogName + ".md"));
|
||||
sourceBlogFile.Delete();
|
||||
}, new BlogOptionsBinder(),
|
||||
new LoggerBinder<EssayScanService>(), new EssayScanServiceBinder(), filenameArgument);
|
||||
}
|
||||
|
||||
private static void AddCompressCommand(RootCommand rootCommand)
|
||||
{
|
||||
Command command = new("compress", "Compress png/jpeg image to webp image to reduce size.");
|
||||
rootCommand.Add(command);
|
||||
|
||||
Option<bool> dryRunOption = new("--dry-run", description: "Dry run the compression task but not write.",
|
||||
getDefaultValue: () => false);
|
||||
command.AddOption(dryRunOption);
|
||||
|
||||
command.SetHandler(ImageCommandHandler,
|
||||
new BlogOptionsBinder(), new LoggerBinder<EssayScanService>(), new LoggerBinder<ImageCompressService>(),
|
||||
new EssayScanServiceBinder(), new ImageCompressServiceBinder(), dryRunOption);
|
||||
}
|
||||
|
||||
private static async Task ImageCommandHandler(IOptions<BlogOptions> _, ILogger<EssayScanService> _1,
|
||||
ILogger<ImageCompressService> _2,
|
||||
IEssayScanService _3, ImageCompressService imageCompressService, bool dryRun)
|
||||
{
|
||||
await imageCompressService.Compress(dryRun);
|
||||
}
|
||||
}
|
||||
|
@@ -8,7 +8,7 @@
|
||||
<link rel="stylesheet" href="YaeBlog.styles.css"/>
|
||||
<link rel="icon" href="images/favicon.ico"/>
|
||||
<link rel="stylesheet" href="globals.css"/>
|
||||
<link rel="stylesheet" href="output.css"/>
|
||||
<link rel="stylesheet" href="tailwind.g.css"/>
|
||||
<HeadOutlet/>
|
||||
</head>
|
||||
|
||||
|
29
YaeBlog/Components/AppreciationCode.razor
Normal file
29
YaeBlog/Components/AppreciationCode.razor
Normal file
@@ -0,0 +1,29 @@
|
||||
<div class="flex flex-wrap justify-center gap-12 max-w-md md:max-w-lg">
|
||||
<div class="relative w-40 h-48 md:w-48 md:w-48 overflow-hidden
|
||||
transition-all duration-300 ease-out hover:scale-125 group">
|
||||
<img
|
||||
src="./images/wechat-code.jpeg"
|
||||
alt="微信赞赏码"
|
||||
class="w-full h-full object-cover"
|
||||
/>
|
||||
<div class="absolute -bottom-8 left-0 right-0 text-center
|
||||
text-white bg-black opacity-60 text-sm font-medium
|
||||
backdrop-blur-sm group-hover:bottom-2 transition-all duration-300">
|
||||
请我喝奶茶<br/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="relative w-40 h-48 md:w-48 md:h-48 overflow-hidden
|
||||
transition-all duration-300 ease-out hover:scale-125 group">
|
||||
<img
|
||||
src="./images/alipay-code.jpeg"
|
||||
alt="支付宝赞赏码"
|
||||
class="w-full h-full object-cover"/>
|
||||
<div class="absolute -bottom-8 left-0 right-0 text-center
|
||||
text-white bg-black opacity-60 text-sm font-medium
|
||||
backdrop-blur-sm group-hover:bottom-2 transition-all duration-300">
|
||||
请我吃晚饭<br/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
@@ -7,11 +7,15 @@
|
||||
<Anchor Address="https://dotnet.microsoft.com" Text="@DotnetVersion"/>
|
||||
驱动。
|
||||
</p>
|
||||
<p class="text-md">
|
||||
Build Commit #
|
||||
<Anchor Address="@BuildCommitUrl" Text="@BuildCommitId"/>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p class="text-md">
|
||||
<a href="https://beian.miit.gov.cn" target="_blank" class="text-black">蜀ICP备2022004429号-1</a>
|
||||
<Anchor Address="https://beian.miit.gov.cn" Text="蜀ICP备2022004429号-1" NewPage="true"/>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -19,4 +23,8 @@
|
||||
@code
|
||||
{
|
||||
private string DotnetVersion => $".NET {Environment.Version}";
|
||||
|
||||
private string BuildCommitId => Environment.GetEnvironmentVariable("COMMIT_ID") ?? "local_build";
|
||||
|
||||
private string BuildCommitUrl => $"https://git.rrricardo.top/jackfiled/YaeBlog/commit/{BuildCommitId}";
|
||||
}
|
||||
|
@@ -1,6 +1,3 @@
|
||||
@using YaeBlog.Models
|
||||
@inject BlogOptions Options
|
||||
|
||||
<div class="px-4 py-8 border border-sky-700 rounded-md bg-sky-200">
|
||||
<div class="flex flex-col gap-3 text-md">
|
||||
<div>
|
||||
@@ -24,6 +21,17 @@
|
||||
Ricardo's Blog
|
||||
</a>”。
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col">
|
||||
<div class="flex justify-center">
|
||||
<p>如果觉得不错的话,可以支持一下作者哦~</p>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-center">
|
||||
<AppreciationCode/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@@ -1,5 +1,8 @@
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:9.0
|
||||
|
||||
ARG COMMIT_ID
|
||||
ENV COMMIT_ID=${COMMIT_ID}
|
||||
|
||||
WORKDIR /app
|
||||
COPY bin/Release/net9.0/publish/ ./
|
||||
COPY source/ ./source/
|
||||
|
12
YaeBlog/Exceptions/BlogCommandException.cs
Normal file
12
YaeBlog/Exceptions/BlogCommandException.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
namespace YaeBlog.Core.Exceptions;
|
||||
|
||||
public class BlogCommandException : Exception
|
||||
{
|
||||
public BlogCommandException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public BlogCommandException(string message, Exception innerException) : base(message, innerException)
|
||||
{
|
||||
}
|
||||
}
|
18
YaeBlog/Extensions/AngleSharpExtensions.cs
Normal file
18
YaeBlog/Extensions/AngleSharpExtensions.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using AngleSharp.Dom;
|
||||
|
||||
namespace YaeBlog.Extensions;
|
||||
|
||||
public static class AngleSharpExtensions
|
||||
{
|
||||
public static IEnumerable<IElement> EnumerateParentElements(this IElement element)
|
||||
{
|
||||
IElement? e = element.ParentElement;
|
||||
|
||||
while (e is not null)
|
||||
{
|
||||
IElement c = e;
|
||||
e = e.ParentElement;
|
||||
yield return c;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,12 +1,20 @@
|
||||
namespace YaeBlog.Models;
|
||||
|
||||
public class BlogContent
|
||||
/// <summary>
|
||||
/// 单个博客文件的所有数据和元数据
|
||||
/// </summary>
|
||||
/// <param name="BlogFile">博客文件</param>
|
||||
/// <param name="Metadata">文件中的MD元数据</param>
|
||||
/// <param name="Content">文件内容</param>
|
||||
/// <param name="IsDraft">是否为草稿</param>
|
||||
/// <param name="Images">博客中使用的文件</param>
|
||||
public record BlogContent(
|
||||
FileInfo BlogFile,
|
||||
MarkdownMetadata Metadata,
|
||||
string Content,
|
||||
bool IsDraft,
|
||||
List<BlogImageInfo> Images,
|
||||
List<FileInfo> NotfoundImages)
|
||||
{
|
||||
public required string FileName { get; init; }
|
||||
|
||||
public required MarkdownMetadata Metadata { get; init; }
|
||||
|
||||
public required string FileContent { get; set; }
|
||||
|
||||
public bool IsDraft { get; set; } = false;
|
||||
public string BlogName => BlogFile.Name.Split('.')[0];
|
||||
}
|
||||
|
@@ -1,10 +1,12 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections;
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
namespace YaeBlog.Models;
|
||||
|
||||
public sealed class BlogContents(ConcurrentBag<BlogContent> drafts, ConcurrentBag<BlogContent> posts)
|
||||
public record BlogContents(ConcurrentBag<BlogContent> Drafts, ConcurrentBag<BlogContent> Posts)
|
||||
: IEnumerable<BlogContent>
|
||||
{
|
||||
public ConcurrentBag<BlogContent> Drafts { get; } = drafts;
|
||||
public IEnumerator<BlogContent> GetEnumerator() => Posts.Concat(Drafts).GetEnumerator();
|
||||
|
||||
public ConcurrentBag<BlogContent> Posts { get; } = posts;
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
}
|
||||
|
44
YaeBlog/Models/BlogImageInfo.cs
Normal file
44
YaeBlog/Models/BlogImageInfo.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
using System.Text;
|
||||
|
||||
namespace YaeBlog.Models;
|
||||
|
||||
public record BlogImageInfo(FileInfo File, long Width, long Height, string MineType, byte[] Content, bool IsUsed)
|
||||
: IComparable<BlogImageInfo>
|
||||
{
|
||||
public int Size => Content.Length;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
StringBuilder builder = new();
|
||||
|
||||
builder.AppendLine($"Blog image {File.Name}:");
|
||||
builder.AppendLine($"\tWidth: {Width}; Height: {Height}");
|
||||
builder.AppendLine($"\tSize: {FormatSize()}");
|
||||
builder.AppendLine($"\tImage Format: {MineType}");
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
public int CompareTo(BlogImageInfo? other)
|
||||
{
|
||||
if (other is null)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
return other.Size.CompareTo(Size);
|
||||
}
|
||||
|
||||
private string FormatSize()
|
||||
{
|
||||
double size = Size;
|
||||
if (size / 1024 > 3)
|
||||
{
|
||||
size /= 1024;
|
||||
|
||||
return size / 1024 > 3 ? $"{size / 1024}MB" : $"{size}KB";
|
||||
}
|
||||
|
||||
return $"{size}B";
|
||||
}
|
||||
}
|
@@ -1,3 +0,0 @@
|
||||
namespace YaeBlog.Models;
|
||||
|
||||
public record struct ImageScanResult(List<FileInfo> UnusedImages, List<FileInfo> NotFoundImages);
|
@@ -1,6 +1,7 @@
|
||||
using AngleSharp;
|
||||
using AngleSharp.Dom;
|
||||
using YaeBlog.Abstraction;
|
||||
using YaeBlog.Extensions;
|
||||
using YaeBlog.Models;
|
||||
|
||||
namespace YaeBlog.Processors;
|
||||
@@ -20,20 +21,21 @@ public sealed class EssayStylesPostRenderProcessor : IPostRenderProcessor
|
||||
|
||||
ApplyGlobalCssStyles(document);
|
||||
BeatifyTable(document);
|
||||
BeatifyList(document);
|
||||
BeatifyInlineCode(document);
|
||||
|
||||
return essay.WithNewHtmlContent(document.DocumentElement.OuterHtml);
|
||||
}
|
||||
|
||||
private readonly Dictionary<string, string> _globalCssStyles = new()
|
||||
{
|
||||
{ "pre", "p-4 bg-slate-300 rounded-sm overflow-x-auto" },
|
||||
{ "pre", "p-4 bg-gray-100 rounded-sm overflow-x-auto" },
|
||||
{ "h2", "text-3xl font-bold py-4" },
|
||||
{ "h3", "text-2xl font-bold py-3" },
|
||||
{ "h4", "text-xl font-bold py-2" },
|
||||
{ "h5", "text-lg font-bold py-1" },
|
||||
{ "p", "p-2" },
|
||||
{ "img", "w-11/12 block mx-auto my-2 rounded-md shadow-md" },
|
||||
{ "ul", "list-disc pl-2" }
|
||||
};
|
||||
|
||||
private void ApplyGlobalCssStyles(IDocument document)
|
||||
@@ -99,4 +101,45 @@ public sealed class EssayStylesPostRenderProcessor : IPostRenderProcessor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void BeatifyList(IDocument document)
|
||||
{
|
||||
foreach (IElement ulElement in from e in document.All
|
||||
where e.LocalName == "ul"
|
||||
select e)
|
||||
{
|
||||
// 首先给<ul>元素添加样式
|
||||
ulElement.ClassList.Add("list-disc ml-10");
|
||||
|
||||
|
||||
foreach (IElement liElement in from e in ulElement.Children
|
||||
where e.LocalName == "li"
|
||||
select e)
|
||||
{
|
||||
// 修改<li>元素中的<p>元素样式
|
||||
// 默认的p-2间距有点太宽了
|
||||
foreach (IElement pElement in from e in liElement.Children
|
||||
where e.LocalName == "p"
|
||||
select e)
|
||||
{
|
||||
pElement.ClassList.Remove("p-2");
|
||||
pElement.ClassList.Add("p-1");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void BeatifyInlineCode(IDocument document)
|
||||
{
|
||||
// 选择不在<pre>元素内的<code>元素
|
||||
// 即行内代码
|
||||
IEnumerable<IElement> inlineCodes = from e in document.All
|
||||
where e.LocalName == "code" && e.EnumerateParentElements().All(p => p.LocalName != "pre")
|
||||
select e;
|
||||
|
||||
foreach (IElement e in inlineCodes)
|
||||
{
|
||||
e.ClassList.Add("bg-gray-100 inline p-1 rounded-xs");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -7,7 +7,8 @@ using YaeBlog.Models;
|
||||
|
||||
namespace YaeBlog.Processors;
|
||||
|
||||
public class ImagePostRenderProcessor(ILogger<ImagePostRenderProcessor> logger,
|
||||
public class ImagePostRenderProcessor(
|
||||
ILogger<ImagePostRenderProcessor> logger,
|
||||
IOptions<BlogOptions> options)
|
||||
: IPostRenderProcessor
|
||||
{
|
||||
@@ -29,22 +30,27 @@ public class ImagePostRenderProcessor(ILogger<ImagePostRenderProcessor> logger,
|
||||
if (attr is not null)
|
||||
{
|
||||
logger.LogDebug("Found image link: '{}'", attr.Value);
|
||||
attr.Value = GenerateImageLink(attr.Value, essay.FileName);
|
||||
attr.Value = GenerateImageLink(attr.Value, essay.FileName, essay.IsDraft);
|
||||
}
|
||||
}
|
||||
|
||||
return essay.WithNewHtmlContent(html.DocumentElement.OuterHtml);
|
||||
}
|
||||
|
||||
public string Name => nameof(ImagePostRenderProcessor);
|
||||
|
||||
private string GenerateImageLink(string filename, string essayFilename)
|
||||
private string GenerateImageLink(string filename, string essayFilename, bool isDraft)
|
||||
{
|
||||
// 如果图片路径中没有包含文件名
|
||||
// 则添加文件名
|
||||
if (!filename.Contains(essayFilename))
|
||||
{
|
||||
filename = Path.Combine(essayFilename, filename);
|
||||
}
|
||||
|
||||
filename = Path.Combine(_options.Root, "posts", filename);
|
||||
filename = isDraft
|
||||
? Path.Combine(_options.Root, "drafts", filename)
|
||||
: Path.Combine(_options.Root, "posts", filename);
|
||||
|
||||
if (!Path.Exists(filename))
|
||||
{
|
||||
|
@@ -1,5 +1,7 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Text.RegularExpressions;
|
||||
using Imageflow.Bindings;
|
||||
using Imageflow.Fluent;
|
||||
using Microsoft.Extensions.Options;
|
||||
using YaeBlog.Abstraction;
|
||||
using YaeBlog.Core.Exceptions;
|
||||
@@ -9,17 +11,30 @@ using YamlDotNet.Serialization;
|
||||
|
||||
namespace YaeBlog.Services;
|
||||
|
||||
public partial class EssayScanService(
|
||||
ISerializer yamlSerializer,
|
||||
IDeserializer yamlDeserializer,
|
||||
IOptions<BlogOptions> blogOptions,
|
||||
ILogger<EssayScanService> logger) : IEssayScanService
|
||||
public partial class EssayScanService : IEssayScanService
|
||||
{
|
||||
private readonly BlogOptions _blogOptions = blogOptions.Value;
|
||||
private readonly BlogOptions _blogOptions;
|
||||
private readonly ISerializer _yamlSerializer;
|
||||
private readonly IDeserializer _yamlDeserializer;
|
||||
private readonly ILogger<EssayScanService> _logger;
|
||||
|
||||
public EssayScanService(ISerializer yamlSerializer,
|
||||
IDeserializer yamlDeserializer,
|
||||
IOptions<BlogOptions> blogOptions,
|
||||
ILogger<EssayScanService> logger)
|
||||
{
|
||||
_yamlSerializer = yamlSerializer;
|
||||
_yamlDeserializer = yamlDeserializer;
|
||||
_logger = logger;
|
||||
_blogOptions = blogOptions.Value;
|
||||
RootDirectory = ValidateRootDirectory();
|
||||
}
|
||||
|
||||
private DirectoryInfo RootDirectory { get; }
|
||||
|
||||
public async Task<BlogContents> ScanContents()
|
||||
{
|
||||
ValidateDirectory(_blogOptions.Root, out DirectoryInfo drafts, out DirectoryInfo posts);
|
||||
ValidateDirectory(out DirectoryInfo drafts, out DirectoryInfo posts);
|
||||
|
||||
return new BlogContents(
|
||||
await ScanContentsInternal(drafts, true),
|
||||
@@ -28,82 +43,92 @@ public partial class EssayScanService(
|
||||
|
||||
public async Task SaveBlogContent(BlogContent content, bool isDraft = true)
|
||||
{
|
||||
ValidateDirectory(_blogOptions.Root, out DirectoryInfo drafts, out DirectoryInfo posts);
|
||||
ValidateDirectory(out DirectoryInfo drafts, out DirectoryInfo posts);
|
||||
|
||||
FileInfo targetFile = isDraft
|
||||
? new FileInfo(Path.Combine(drafts.FullName, content.FileName + ".md"))
|
||||
: new FileInfo(Path.Combine(posts.FullName, content.FileName + ".md"));
|
||||
|
||||
if (!isDraft)
|
||||
{
|
||||
content.Metadata.Date = DateTime.Now;
|
||||
}
|
||||
? new FileInfo(Path.Combine(drafts.FullName, content.BlogName + ".md"))
|
||||
: new FileInfo(Path.Combine(posts.FullName, content.BlogName + ".md"));
|
||||
|
||||
if (targetFile.Exists)
|
||||
{
|
||||
logger.LogWarning("Blog {} exists, overriding.", targetFile.Name);
|
||||
_logger.LogWarning("Blog {} exists, overriding.", targetFile.Name);
|
||||
}
|
||||
|
||||
await using StreamWriter writer = targetFile.CreateText();
|
||||
|
||||
await writer.WriteAsync("---\n");
|
||||
await writer.WriteAsync(yamlSerializer.Serialize(content.Metadata));
|
||||
await writer.WriteAsync(_yamlSerializer.Serialize(content.Metadata));
|
||||
await writer.WriteAsync("---\n");
|
||||
|
||||
if (isDraft)
|
||||
if (string.IsNullOrEmpty(content.Content) && isDraft)
|
||||
{
|
||||
// 如果博客为操作且内容为空
|
||||
// 创建简介隔断符号
|
||||
await writer.WriteLineAsync("<!--more-->");
|
||||
}
|
||||
else
|
||||
{
|
||||
await writer.WriteAsync(content.FileContent);
|
||||
await writer.WriteAsync(content.Content);
|
||||
}
|
||||
|
||||
// 保存图片文件
|
||||
await Task.WhenAll(from image in content.Images
|
||||
select File.WriteAllBytesAsync(image.File.FullName, image.Content));
|
||||
}
|
||||
|
||||
private record struct BlogResult(
|
||||
FileInfo BlogFile,
|
||||
string BlogContent,
|
||||
List<BlogImageInfo> Images,
|
||||
List<FileInfo> NotFoundImages);
|
||||
|
||||
private async Task<ConcurrentBag<BlogContent>> ScanContentsInternal(DirectoryInfo directory, bool isDraft)
|
||||
{
|
||||
// 扫描以md结果的但是不是隐藏文件的文件
|
||||
// 扫描以md结尾且不是隐藏文件的文件
|
||||
IEnumerable<FileInfo> markdownFiles = from file in directory.EnumerateFiles()
|
||||
where file.Extension == ".md" && !file.Name.StartsWith('.')
|
||||
select file;
|
||||
|
||||
ConcurrentBag<(string, string)> fileContents = [];
|
||||
ConcurrentBag<BlogResult> fileContents = [];
|
||||
|
||||
await Parallel.ForEachAsync(markdownFiles, async (file, token) =>
|
||||
{
|
||||
using StreamReader reader = file.OpenText();
|
||||
fileContents.Add((file.Name, await reader.ReadToEndAsync(token)));
|
||||
string blogName = file.Name.Split('.')[0];
|
||||
string blogContent = await reader.ReadToEndAsync(token);
|
||||
ImageResult imageResult =
|
||||
await ScanImagePreBlog(directory, blogName,
|
||||
blogContent);
|
||||
|
||||
fileContents.Add(new BlogResult(file, blogContent, imageResult.Images, imageResult.NotfoundImages));
|
||||
});
|
||||
|
||||
ConcurrentBag<BlogContent> contents = [];
|
||||
|
||||
await Task.Run(() =>
|
||||
{
|
||||
foreach ((string filename, string content) in fileContents)
|
||||
foreach (BlogResult blog in fileContents)
|
||||
{
|
||||
int endPos = content.IndexOf("---", 4, StringComparison.Ordinal);
|
||||
if (!content.StartsWith("---") || endPos is -1 or 0)
|
||||
int endPos = blog.BlogContent.IndexOf("---", 4, StringComparison.Ordinal);
|
||||
if (!blog.BlogContent.StartsWith("---") || endPos is -1 or 0)
|
||||
{
|
||||
logger.LogWarning("Failed to parse metadata from {}, skipped.", filename);
|
||||
_logger.LogWarning("Failed to parse metadata from {}, skipped.", blog.BlogFile.Name);
|
||||
return;
|
||||
}
|
||||
|
||||
string metadataString = content[4..endPos];
|
||||
string metadataString = blog.BlogContent[4..endPos];
|
||||
|
||||
try
|
||||
{
|
||||
MarkdownMetadata metadata = yamlDeserializer.Deserialize<MarkdownMetadata>(metadataString);
|
||||
logger.LogDebug("Scan metadata title: '{}' for {}.", metadata.Title, filename);
|
||||
MarkdownMetadata metadata = _yamlDeserializer.Deserialize<MarkdownMetadata>(metadataString);
|
||||
_logger.LogDebug("Scan metadata title: '{}' for {}.", metadata.Title, blog.BlogFile.Name);
|
||||
|
||||
contents.Add(new BlogContent
|
||||
{
|
||||
FileName = filename[..^3], Metadata = metadata, FileContent = content[(endPos + 3)..],
|
||||
IsDraft = isDraft
|
||||
});
|
||||
contents.Add(new BlogContent(blog.BlogFile, metadata, blog.BlogContent[(endPos + 3)..], isDraft,
|
||||
blog.Images, blog.NotFoundImages));
|
||||
}
|
||||
catch (YamlException e)
|
||||
{
|
||||
logger.LogWarning("Failed to parser metadata from {} due to {}, skipping", filename, e);
|
||||
_logger.LogWarning("Failed to parser metadata from {} due to {}, skipping", blog.BlogFile.Name, e);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -111,99 +136,96 @@ public partial class EssayScanService(
|
||||
return contents;
|
||||
}
|
||||
|
||||
public async Task<ImageScanResult> ScanImages()
|
||||
private record struct ImageResult(List<BlogImageInfo> Images, List<FileInfo> NotfoundImages);
|
||||
|
||||
private async Task<ImageResult> ScanImagePreBlog(DirectoryInfo directory, string blogName, string content)
|
||||
{
|
||||
BlogContents contents = await ScanContents();
|
||||
ValidateDirectory(_blogOptions.Root, out DirectoryInfo drafts, out DirectoryInfo posts);
|
||||
MatchCollection matchResult = ImagePattern.Matches(content);
|
||||
DirectoryInfo imageDirectory = new(Path.Combine(directory.FullName, blogName));
|
||||
|
||||
List<FileInfo> unusedFiles = [];
|
||||
List<FileInfo> notFoundFiles = [];
|
||||
Dictionary<string, bool> usedImages = imageDirectory.Exists
|
||||
? imageDirectory.EnumerateFiles().ToDictionary(file => file.FullName, _ => false)
|
||||
: [];
|
||||
List<FileInfo> notFoundImages = [];
|
||||
|
||||
ImageScanResult draftResult = await ScanUnusedImagesInternal(contents.Drafts, drafts);
|
||||
ImageScanResult postResult = await ScanUnusedImagesInternal(contents.Posts, posts);
|
||||
|
||||
unusedFiles.AddRange(draftResult.UnusedImages);
|
||||
notFoundFiles.AddRange(draftResult.NotFoundImages);
|
||||
unusedFiles.AddRange(postResult.UnusedImages);
|
||||
notFoundFiles.AddRange(postResult.NotFoundImages);
|
||||
|
||||
return new ImageScanResult(unusedFiles, notFoundFiles);
|
||||
}
|
||||
|
||||
private static Task<ImageScanResult> ScanUnusedImagesInternal(IEnumerable<BlogContent> contents,
|
||||
DirectoryInfo root)
|
||||
{
|
||||
ConcurrentBag<FileInfo> unusedImage = [];
|
||||
ConcurrentBag<FileInfo> notFoundImage = [];
|
||||
|
||||
Parallel.ForEach(contents, content =>
|
||||
foreach (Match match in matchResult)
|
||||
{
|
||||
MatchCollection result = ImagePattern.Matches(content.FileContent);
|
||||
DirectoryInfo imageDirectory = new(Path.Combine(root.FullName, content.FileName));
|
||||
string imageName = match.Groups[1].Value;
|
||||
|
||||
Dictionary<string, bool> usedDictionary;
|
||||
// 判断md文件中的图片名称中是否包含文件夹名称
|
||||
// 例如 blog-1/image.png 或者 image.png
|
||||
// 如果不带文件夹名称
|
||||
// 默认添加同博客名文件夹
|
||||
FileInfo usedFile = imageName.Contains(blogName)
|
||||
? new FileInfo(Path.Combine(directory.FullName, imageName))
|
||||
: new FileInfo(Path.Combine(directory.FullName, blogName, imageName));
|
||||
|
||||
if (imageDirectory.Exists)
|
||||
if (usedImages.TryGetValue(usedFile.FullName, out _))
|
||||
{
|
||||
usedDictionary = (from file in imageDirectory.EnumerateFiles()
|
||||
select new KeyValuePair<string, bool>(file.FullName, false)).ToDictionary();
|
||||
usedImages[usedFile.FullName] = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
usedDictionary = [];
|
||||
notFoundImages.Add(usedFile);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (Match match in result)
|
||||
{
|
||||
string imageName = match.Groups[1].Value;
|
||||
List<BlogImageInfo> images = (await Task.WhenAll((from pair in usedImages
|
||||
select GetImageInfo(new FileInfo(pair.Key), pair.Value)).ToArray())).ToList();
|
||||
|
||||
FileInfo usedFile = imageName.Contains(content.FileName)
|
||||
? new FileInfo(Path.Combine(root.FullName, imageName))
|
||||
: new FileInfo(Path.Combine(root.FullName, content.FileName, imageName));
|
||||
return new ImageResult(images, notFoundImages);
|
||||
}
|
||||
|
||||
if (usedDictionary.TryGetValue(usedFile.FullName, out _))
|
||||
{
|
||||
usedDictionary[usedFile.FullName] = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
notFoundImage.Add(usedFile);
|
||||
}
|
||||
}
|
||||
private static async Task<BlogImageInfo> GetImageInfo(FileInfo file, bool isUsed)
|
||||
{
|
||||
byte[] image = await File.ReadAllBytesAsync(file.FullName);
|
||||
|
||||
foreach (KeyValuePair<string, bool> pair in usedDictionary.Where(p => !p.Value))
|
||||
{
|
||||
unusedImage.Add(new FileInfo(pair.Key));
|
||||
}
|
||||
});
|
||||
if (file.Extension is ".jpg" or ".jpeg" or ".png")
|
||||
{
|
||||
ImageInfo imageInfo =
|
||||
await ImageJob.GetImageInfoAsync(MemorySource.Borrow(image), SourceLifetime.NowOwnedAndDisposedByTask);
|
||||
|
||||
return Task.FromResult(new ImageScanResult(unusedImage.ToList(), notFoundImage.ToList()));
|
||||
return new BlogImageInfo(file, imageInfo.ImageWidth, imageInfo.ImageWidth, imageInfo.PreferredMimeType,
|
||||
image, isUsed);
|
||||
}
|
||||
|
||||
return new BlogImageInfo(file, 0, 0, file.Extension switch
|
||||
{
|
||||
"svg" => "image/svg",
|
||||
"avif" => "image/avif",
|
||||
_ => string.Empty
|
||||
}, image, isUsed);
|
||||
}
|
||||
|
||||
[GeneratedRegex(@"\!\[.*?\]\((.*?)\)")]
|
||||
private static partial Regex ImagePattern { get; }
|
||||
|
||||
private void ValidateDirectory(string root, out DirectoryInfo drafts, out DirectoryInfo posts)
|
||||
|
||||
private DirectoryInfo ValidateRootDirectory()
|
||||
{
|
||||
root = Path.Combine(Environment.CurrentDirectory, root);
|
||||
DirectoryInfo rootDirectory = new(root);
|
||||
DirectoryInfo rootDirectory = new(Path.Combine(Environment.CurrentDirectory, _blogOptions.Root));
|
||||
|
||||
if (!rootDirectory.Exists)
|
||||
{
|
||||
throw new BlogFileException($"'{root}' is not a directory.");
|
||||
throw new BlogFileException($"'{_blogOptions.Root}' is not a directory.");
|
||||
}
|
||||
|
||||
if (rootDirectory.EnumerateDirectories().All(dir => dir.Name != "drafts"))
|
||||
return rootDirectory;
|
||||
}
|
||||
|
||||
private void ValidateDirectory(out DirectoryInfo drafts, out DirectoryInfo posts)
|
||||
{
|
||||
if (RootDirectory.EnumerateDirectories().All(dir => dir.Name != "drafts"))
|
||||
{
|
||||
throw new BlogFileException($"'{root}/drafts' not exists.");
|
||||
throw new BlogFileException($"'{_blogOptions.Root}/drafts' not exists.");
|
||||
}
|
||||
|
||||
if (rootDirectory.EnumerateDirectories().All(dir => dir.Name != "posts"))
|
||||
if (RootDirectory.EnumerateDirectories().All(dir => dir.Name != "posts"))
|
||||
{
|
||||
throw new BlogFileException($"'{root}/posts' not exists.");
|
||||
throw new BlogFileException($"'{_blogOptions.Root}/posts' not exists.");
|
||||
}
|
||||
|
||||
drafts = new DirectoryInfo(Path.Combine(root, "drafts"));
|
||||
posts = new DirectoryInfo(Path.Combine(root, "posts"));
|
||||
drafts = new DirectoryInfo(Path.Combine(_blogOptions.Root, "drafts"));
|
||||
posts = new DirectoryInfo(Path.Combine(_blogOptions.Root, "posts"));
|
||||
}
|
||||
}
|
||||
|
119
YaeBlog/Services/ImageCompressService.cs
Normal file
119
YaeBlog/Services/ImageCompressService.cs
Normal file
@@ -0,0 +1,119 @@
|
||||
using Imageflow.Fluent;
|
||||
using YaeBlog.Abstraction;
|
||||
using YaeBlog.Core.Exceptions;
|
||||
using YaeBlog.Models;
|
||||
|
||||
namespace YaeBlog.Services;
|
||||
|
||||
public sealed class ImageCompressService(IEssayScanService essayScanService, ILogger<ImageCompressService> logger)
|
||||
{
|
||||
private record struct CompressResult(BlogImageInfo ImageInfo, byte[] CompressContent);
|
||||
|
||||
public async Task<List<BlogImageInfo>> ScanUsedImages()
|
||||
{
|
||||
BlogContents contents = await essayScanService.ScanContents();
|
||||
List<BlogImageInfo> originalImages = (from content in contents.Posts.Concat(contents.Drafts)
|
||||
from image in content.Images
|
||||
where image.IsUsed
|
||||
select image).ToList();
|
||||
|
||||
originalImages.Sort();
|
||||
|
||||
return originalImages;
|
||||
}
|
||||
|
||||
public async Task Compress(bool dryRun)
|
||||
{
|
||||
BlogContents contents = await essayScanService.ScanContents();
|
||||
|
||||
// 筛选需要压缩的图片
|
||||
// 即图片被博客使用且是jpeg/png格式
|
||||
List<BlogContent> needCompressContents = (from content in contents
|
||||
where content.Images.Any(i => i is { IsUsed: true } and { File.Extension: ".jpg" or ".jpeg" or ".png" })
|
||||
select content).ToList();
|
||||
|
||||
if (needCompressContents.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int uncompressedSize = 0;
|
||||
int compressedSize = 0;
|
||||
List<BlogContent> compressedContent = new(needCompressContents.Count);
|
||||
|
||||
foreach (BlogContent content in needCompressContents)
|
||||
{
|
||||
List<BlogImageInfo> uncompressedImages = (from image in content.Images
|
||||
where image is { IsUsed: true } and { File.Extension: ".jpg" or ".jpeg" or ".png" }
|
||||
select image).ToList();
|
||||
|
||||
uncompressedSize += uncompressedImages.Select(i => i.Size).Sum();
|
||||
|
||||
foreach (BlogImageInfo image in uncompressedImages)
|
||||
{
|
||||
logger.LogInformation("Uncompressed image: {} belonging to blog {}.", image.File.Name,
|
||||
content.BlogName);
|
||||
}
|
||||
|
||||
CompressResult[] compressedImages = (await Task.WhenAll(from image in uncompressedImages
|
||||
select Task.Run(async () => new CompressResult(image, await ConvertToWebp(image))))).ToArray();
|
||||
|
||||
compressedSize += compressedImages.Select(i => i.CompressContent.Length).Sum();
|
||||
|
||||
// 直接在原有的图片列表上添加图片
|
||||
List<BlogImageInfo> images = content.Images.Concat(from r in compressedImages
|
||||
select r.ImageInfo with
|
||||
{
|
||||
File = new FileInfo(r.ImageInfo.File.FullName.Split('.')[0] + ".webp"),
|
||||
Content = r.CompressContent,
|
||||
MineType = "image/webp"
|
||||
}).ToList();
|
||||
// 修改文本
|
||||
string blogContent = compressedImages.Aggregate(content.Content, (c, r) =>
|
||||
{
|
||||
string originalName = r.ImageInfo.File.Name;
|
||||
string outputName = originalName.Split('.')[0] + ".webp";
|
||||
|
||||
return c.Replace(originalName, outputName);
|
||||
});
|
||||
|
||||
compressedContent.Add(content with { Images = images, Content = blogContent });
|
||||
}
|
||||
|
||||
logger.LogInformation("Compression ratio: {}%.", (double)compressedSize / uncompressedSize * 100.0);
|
||||
|
||||
if (dryRun is false)
|
||||
{
|
||||
await Task.WhenAll(from content in compressedContent
|
||||
select essayScanService.SaveBlogContent(content, content.IsDraft));
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<byte[]> ConvertToWebp(BlogImageInfo image)
|
||||
{
|
||||
using ImageJob job = new();
|
||||
BuildJobResult result = await job.Decode(MemorySource.Borrow(image.Content))
|
||||
.Branch(f => f.EncodeToBytes(new WebPLosslessEncoder()))
|
||||
.EncodeToBytes(new WebPLossyEncoder(75))
|
||||
.Finish()
|
||||
.InProcessAsync();
|
||||
|
||||
// 超过128KB的图片使用有损压缩
|
||||
// 反之使用无损压缩
|
||||
|
||||
ArraySegment<byte>? losslessImage = result.TryGet(1)?.TryGetBytes();
|
||||
ArraySegment<byte>? lossyImage = result.TryGet(2)?.TryGetBytes();
|
||||
|
||||
if (image.Size <= 128 * 1024 && losslessImage.HasValue)
|
||||
{
|
||||
return losslessImage.Value.ToArray();
|
||||
}
|
||||
|
||||
if (lossyImage.HasValue)
|
||||
{
|
||||
return lossyImage.Value.ToArray();
|
||||
}
|
||||
|
||||
throw new BlogCommandException($"Failed to convert {image.File.Name} to webp format: return value is null.");
|
||||
}
|
||||
}
|
@@ -41,14 +41,14 @@ public partial class RendererService(
|
||||
uint wordCount = GetWordCount(content);
|
||||
BlogEssay essay = new()
|
||||
{
|
||||
Title = content.Metadata.Title ?? content.FileName,
|
||||
FileName = content.FileName,
|
||||
Title = content.Metadata.Title ?? content.BlogName,
|
||||
FileName = content.BlogName,
|
||||
IsDraft = content.IsDraft,
|
||||
Description = GetDescription(content),
|
||||
WordCount = wordCount,
|
||||
ReadTime = CalculateReadTime(wordCount),
|
||||
PublishTime = content.Metadata.Date ?? DateTime.Now,
|
||||
HtmlContent = content.FileContent
|
||||
HtmlContent = content.Content
|
||||
};
|
||||
|
||||
if (content.Metadata.Tags is not null)
|
||||
@@ -156,17 +156,17 @@ public partial class RendererService(
|
||||
private string GetDescription(BlogContent content)
|
||||
{
|
||||
const string delimiter = "<!--more-->";
|
||||
int pos = content.FileContent.IndexOf(delimiter, StringComparison.Ordinal);
|
||||
int pos = content.Content.IndexOf(delimiter, StringComparison.Ordinal);
|
||||
bool breakSentence = false;
|
||||
|
||||
if (pos == -1)
|
||||
{
|
||||
// 自动截取前50个字符
|
||||
pos = content.FileContent.Length < 50 ? content.FileContent.Length : 50;
|
||||
pos = content.Content.Length < 50 ? content.Content.Length : 50;
|
||||
breakSentence = true;
|
||||
}
|
||||
|
||||
string rawContent = content.FileContent[..pos];
|
||||
string rawContent = content.Content[..pos];
|
||||
MatchCollection matches = DescriptionPattern.Matches(rawContent);
|
||||
|
||||
StringBuilder builder = new();
|
||||
@@ -182,18 +182,18 @@ public partial class RendererService(
|
||||
|
||||
string description = builder.ToString();
|
||||
|
||||
logger.LogDebug("Description of {} is {}.", content.FileName,
|
||||
logger.LogDebug("Description of {} is {}.", content.BlogName,
|
||||
description);
|
||||
return description;
|
||||
}
|
||||
|
||||
private uint GetWordCount(BlogContent content)
|
||||
{
|
||||
int count = (from c in content.FileContent
|
||||
int count = (from c in content.Content
|
||||
where char.IsLetterOrDigit(c)
|
||||
select c).Count();
|
||||
|
||||
logger.LogDebug("Word count of {} is {}", content.FileName,
|
||||
logger.LogDebug("Word count of {} is {}", content.BlogName,
|
||||
count);
|
||||
return (uint)count;
|
||||
}
|
||||
|
@@ -1,6 +1,9 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ImageFlow.NativeRuntime.ubuntu-x86_64" Version="2.1.0-rc11" Condition="$([MSBuild]::IsOsPlatform('Linux'))"/>
|
||||
<PackageReference Include="ImageFlow.NativeRuntime.osx-arm64" Version="2.1.0-rc11" Condition="$([MSBuild]::IsOsPlatform('OSX'))"/>
|
||||
<PackageReference Include="ImageFlow.Net" Version="0.13.2"/>
|
||||
<PackageReference Include="System.CommandLine" Version="2.0.0-beta4.22272.1"/>
|
||||
<PackageReference Include="AngleSharp" Version="1.1.0"/>
|
||||
<PackageReference Include="Markdig" Version="0.38.0"/>
|
||||
@@ -13,7 +16,7 @@
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<Target Name="EnsurePnpmInstalled" BeforeTargets="Build">
|
||||
<Target Name="EnsurePnpmInstalled" BeforeTargets="BeforeBuild">
|
||||
<Message Importance="low" Text="Ensure pnpm is installed..."/>
|
||||
<Exec Command="pnpm --version" ContinueOnError="true">
|
||||
<Output TaskParameter="ExitCode" PropertyName="ErrorCode"/>
|
||||
@@ -25,9 +28,13 @@
|
||||
<Exec Command="pnpm install"/>
|
||||
</Target>
|
||||
|
||||
<Target Name="TailwindGenerate" AfterTargets="EnsurePnpmInstalled">
|
||||
<Target Name="TailwindGenerate" AfterTargets="EnsurePnpmInstalled" BeforeTargets="BeforeBuild" Condition="'$(_IsPublishing)' == 'yes'">
|
||||
<Message Importance="normal" Text="Generate css files using tailwind..."/>
|
||||
<Exec Command="pnpm tailwind -i wwwroot/input.css -o wwwroot/output.css"/>
|
||||
<Exec Command="pnpm tailwindcss -i wwwroot/tailwind.css -o $(IntermediateOutputPath)tailwind.g.css"/>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="$(IntermediateOutputPath)tailwind.g.css" Visible="false" TargetPath="wwwroot/tailwind.g.css"/>
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
|
||||
</Project>
|
||||
|
@@ -1,12 +1,15 @@
|
||||
{
|
||||
"name": "YaeBlog",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"scripts": {},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"tailwindcss": "^3.4.16"
|
||||
}
|
||||
"name": "yae-blog",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"scripts": {
|
||||
"dev": "tailwindcss -i wwwroot/tailwind.css -o wwwroot/tailwind.g.css -w"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"tailwindcss": "^4.0.0",
|
||||
"@tailwindcss/cli": "^4.0.0"
|
||||
}
|
||||
}
|
||||
|
1107
YaeBlog/pnpm-lock.yaml
generated
1107
YaeBlog/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
369
YaeBlog/source/drafts/hpc-2025-cuda.md
Normal file
369
YaeBlog/source/drafts/hpc-2025-cuda.md
Normal file
@@ -0,0 +1,369 @@
|
||||
---
|
||||
title: High Performance Computing 25 SP NVIDIA
|
||||
date: 2025-04-24T19:02:36.1077330+08:00
|
||||
tags:
|
||||
- 高性能计算
|
||||
- 学习资料
|
||||
---
|
||||
|
||||
|
||||
Fxxk you, NVIDIA!
|
||||
|
||||
<!--more-->
|
||||
|
||||
CPU/GPU Parallelism:
|
||||
|
||||
Moore's Law gives you more and more transistors:
|
||||
|
||||
- CPU strategy: make the workload (one compute thread) run as fast as possible.
|
||||
- GPU strategy: make the workload (as many threads as possible) run as fast as possible.
|
||||
|
||||
GPU Architecture:
|
||||
|
||||
- Massively Parallel
|
||||
- Power Efficient
|
||||
- Memory Bandwidth
|
||||
- Commercially Viable Parallelism
|
||||
- Not dependent on large caches for performance
|
||||
|
||||

|
||||
|
||||
## Nvidia GPU Generations
|
||||
|
||||
- 2006: G80-based GeForce 8800
|
||||
- 2008: GT200-based GeForce GTX 280
|
||||
- 2010: Fermi
|
||||
- 2012: Kepler
|
||||
- 2014: Maxwell
|
||||
- 2016: Pascal
|
||||
- 2017: Volta
|
||||
- 2021: Ampere
|
||||
- 2022: Hopper
|
||||
- 2024: Blackwell
|
||||
|
||||
#### 2006: G80 Terminology
|
||||
|
||||
SP: Streaming Processor, scalar ALU for a single CUDA thread
|
||||
|
||||
SPA: Stream Processor Array
|
||||
|
||||
SM: Streaming Multiprocessor, containing of 8 SP
|
||||
|
||||
TPC: Texture Processor Cluster: 2 SM + TEX
|
||||
|
||||

|
||||
|
||||
Design goal: performance per millimeter
|
||||
|
||||
For GPUs, performance is throughput, so hide latency with computation not cache.
|
||||
|
||||
So this is single instruction multiple thread (SIMT).
|
||||
|
||||
**Thread Life Cycle**:
|
||||
|
||||
Grid is launched on the SPA and thread blocks are serially distributed to all the SM.
|
||||
|
||||

|
||||
|
||||
**SIMT Thread Execution**:
|
||||
|
||||
Groups of 32 threads formed into warps. Threads in the same wraps always executing same instructions. And some threads may become inactive when code path diverges so the hardware **automatically Handles divergence**.
|
||||
|
||||
Warps are the primitive unit of scheduling.
|
||||
|
||||
> SIMT execution is an implementation choice. As sharing control logic leaves more space for ALUs.
|
||||
|
||||
**SM Warp Scheduling**:
|
||||
|
||||
SM hardware implements zero-overhead warp scheduling:
|
||||
|
||||
- Warps whose next instruction has its operands ready for consumption are eligible for execution.
|
||||
- Eligible warps are selected for execution on a prioritized scheduling policy.
|
||||
|
||||
> If 4 clock cycles needed to dispatch the same instructions for all threads in a warp, and one global memory access is needed for every 4 instructions and memory latency is 200 cycles. So there should be 200 / (4 * 4) =12.5 (13) warps to fully tolerate the memory latency
|
||||
|
||||
The SM warp scheduling use scoreboard and similar things.
|
||||
|
||||
**Granularity Consideration**:
|
||||
|
||||
Consider that int the G80 GPU, one SM can run 768 threads and 8 thread blocks, which is the best tiles to matrix multiplication: 16 * 16 = 256 and in one SM there can be 3 thread block which fully use the threads.
|
||||
|
||||
### 2008: GT200 Architecture
|
||||
|
||||

|
||||
|
||||
### 2010: Fermi GF100 GPU
|
||||
|
||||
**Fermi SM**:
|
||||
|
||||

|
||||
|
||||
There are 32 cores per SM and 512 cores in total, and introduce 64KB configureable L1/ shared memory.
|
||||
|
||||
Decouple internal execution resource and dual issue pipelines to select two warps.
|
||||
|
||||
And in Fermi, the debut the Parallel Thread eXecution(PTX) 2.0 ISA.
|
||||
|
||||
### 2012 Kepler GK 110
|
||||
|
||||

|
||||
|
||||
### 2014 Maxwell
|
||||
|
||||
4 GPCs and 16 SMM.
|
||||
|
||||

|
||||
|
||||
### 2016 Pascal
|
||||
|
||||
No thing to pay attention to.
|
||||
|
||||
### 2017 Volta
|
||||
|
||||
First introduce the tensor core, which is the ASIC to calculate matrix multiplication.
|
||||
|
||||
### 2021 Ampere
|
||||
|
||||
The GA100 SM:
|
||||
|
||||

|
||||
|
||||
### 2022 Hopper
|
||||
|
||||
Introduce the GH200 Grace Hopper Superchip:
|
||||
|
||||

|
||||
|
||||
A system contains a CPU and GPU which is linked by a NVLink technology.
|
||||
|
||||
And this system can scale out for machine learning.
|
||||
|
||||

|
||||
|
||||
Memory access across the NVLink:
|
||||
|
||||
- GPU to local CPU
|
||||
- GPU to peer GPU
|
||||
- GPU to peer CPU
|
||||
|
||||

|
||||
|
||||
These operations can be handled by hardware accelerated memory coherency. Previously, there are separate page table for CPU and GPU but for GPU to access memory in both CPU and GPU, CPU and GPU can use the same page table.
|
||||
|
||||

|
||||
|
||||
### 2025 Blackwell
|
||||
|
||||

|
||||
|
||||
### Compute Capability
|
||||
|
||||
The software version to show hardware version features and specifications.
|
||||
|
||||
## G80 Memory Hierarchy
|
||||
|
||||
### Memory Space
|
||||
|
||||
Each thread can
|
||||
|
||||
- Read and write per-thread registers.
|
||||
- Read and write per-thread local memory.
|
||||
- Read and write pre-block shared memory.
|
||||
- Read and write pre-grid global memory.
|
||||
- Read only pre-grid constant memory.
|
||||
- Read only pre-grid texture memory.
|
||||
|
||||

|
||||
|
||||
Parallel Memory Sharing:
|
||||
|
||||
- Local memory is per-thread and mainly for auto variables and register spill.
|
||||
- Share memory is pre-block which can be used for inter thread communication.
|
||||
- Global memory is pre-application which can be used for inter grid communication.
|
||||
|
||||
### SM Memory Architecture
|
||||
|
||||

|
||||
|
||||
Threads in a block share data and results in memory and shared memory.
|
||||
|
||||
Shared memory is dynamically allocated to blocks which is one of the limiting resources.
|
||||
|
||||
### SM Register File
|
||||
|
||||
Register File(RF): there are 32KB, or 8192 entries, register for each SM in G80 GPU.
|
||||
|
||||
The tex pipeline and local/store pipeline can read and write register file.
|
||||
|
||||
Registers are dynamically partitioned across all blocks assigned to the SM. Once assigned to a block the register is **not** accessible by threads in other blocks and each thread in the same block only access registers assigned to itself.
|
||||
|
||||
For a matrix multiplication example:
|
||||
|
||||
- If one thread uses 10 registers and one block has 16x16 threads, each SM can contains three thread blocks as one thread blocks need 16 * 16 * 10 =2,560 registers and 3 * 2560 < 8192.
|
||||
- But if each thread need 11 registers, one SM can only contains two blocks once as 8192 < 2816 * 3.
|
||||
|
||||
More on dynamic partitioning: dynamic partitioning gives more flexibility to compilers and programmers.
|
||||
|
||||
1. A smaller number of threads that require many registers each.
|
||||
2. A large number of threads that require few registers each.
|
||||
|
||||
So there is a tradeoff between instruction level parallelism and thread level parallelism.
|
||||
|
||||
### Parallel Memory Architecture
|
||||
|
||||
In a parallel machine, many threads access memory. So memory is divided into banks to achieve high bandwidth.
|
||||
|
||||
Each bank can service one address per cycle. If multiple simultaneous accesses to a bank result in a bank conflict.
|
||||
|
||||
Shared memory bank conflicts:
|
||||
|
||||
- The fast cases:
|
||||
- All threads of a half-warp access different banks, there's no back conflict.
|
||||
- All threads of a half-warp access the identical address ,there is no bank conflict (by broadcasting).
|
||||
- The slow cases:
|
||||
- Multiple threads in the same half-warp access the same bank
|
||||
|
||||
## Memory in Later Generations
|
||||
|
||||
### Fermi Architecture
|
||||
|
||||
**Unified Addressing Model** allows local, shared and global memory access using the same address space.
|
||||
|
||||

|
||||
|
||||
**Configurable Caches** allows programmers to configure the size if L1 cache and the shared memory.
|
||||
|
||||
The L1 cache works as a counterpart to shared memory:
|
||||
|
||||
- Shared memory improves memory access for algorithms with well defined memory access.
|
||||
- L1 cache improves memory access for irregular algorithms where data addresses are not known before hand.
|
||||
|
||||
### Pascal Architecture
|
||||
|
||||
**High Bandwidth Memory**: a technology which enables multiple layers of DRAM components to be integrated vertically on the package along with the GPU.
|
||||
|
||||

|
||||
|
||||
**Unified Memory** provides a single and unified virtual address space for accessing all CPU and GPU memory in the system.
|
||||
|
||||
And the CUDA system software doesn't need to synchronize all managed memory allocations to the GPU before each kernel launch. This is enabled by **memory page faulting**.
|
||||
|
||||
## Advanced GPU Features
|
||||
|
||||
### GigaThread
|
||||
|
||||
Enable concurrent kernel execution:
|
||||
|
||||

|
||||
|
||||
And provides dual **Streaming Data Transfer** engines to enable streaming data transfer, a.k.a direct memory access.
|
||||
|
||||

|
||||
|
||||
### GPUDirect
|
||||
|
||||

|
||||
|
||||
### GPU Boost
|
||||
|
||||
GPU Boost works through real time hardware monitoring as opposed to application based profiles. It attempts to find what is the appropriate GPU frequency and voltage for a given moment in time.
|
||||
|
||||
### SMX Architectural Details
|
||||
|
||||
Each unit contains four warp schedulers.
|
||||
|
||||
Scheduling functions:
|
||||
|
||||
- Register scoreboard for long latency operations.
|
||||
- Inter-warp scheduling decisions.
|
||||
- Thread block level scheduling.
|
||||
|
||||
### Improving Programmability
|
||||
|
||||

|
||||
|
||||
**Dynamic Parallelism**: The ability to launch new grids from the GPU.
|
||||
|
||||
And then introduce data-dependent parallelism and dynamic work generation and even batched and nested parallelism.
|
||||
|
||||
The cpu controlled work batching:
|
||||
|
||||
- CPU program limited by single point of control.
|
||||
- Can run at most 10s of threads.
|
||||
- CPU is fully consumed with controlling launches.
|
||||
|
||||

|
||||
|
||||
Batching via dynamic parallelism:
|
||||
|
||||
- Move top-level loops to GPUs.
|
||||
- Run thousands of independent tasks.
|
||||
- Release CPU for other work.
|
||||
|
||||

|
||||
|
||||
### Grid Management Unit
|
||||
|
||||

|
||||
|
||||
Fermi Concurrency:
|
||||
|
||||
- Up to 16 grids can run at once.
|
||||
- But CUDA streams multiplex into a single queue.
|
||||
- Overlap only at stream edge.
|
||||
|
||||
Kepler Improved Concurrency:
|
||||
|
||||
- Up to 32 grids can run at once.
|
||||
- One work queue per stream.
|
||||
- Concurrency at full-stream level.
|
||||
- No inter-stream dependencies.
|
||||
|
||||
It is called as **Hyper-Q**.
|
||||
|
||||
Without Hyper-Q:
|
||||
|
||||

|
||||
|
||||
With Hyper-Q:
|
||||
|
||||

|
||||
|
||||
In pascal, **asynchronous concurrent computing** is introduced.
|
||||
|
||||

|
||||
|
||||
### NVLink: High-Speed Node Network
|
||||
|
||||

|
||||
|
||||
> The *consumer* prefix means the product is designed for gamers.
|
||||
>
|
||||
> The *big* prefix means the product is designed for HPC.
|
||||
|
||||
### Preemption
|
||||
|
||||
Pascal can actually preempt at the lowest level, the instruction level.
|
||||
|
||||

|
||||
|
||||
### Tensor Core
|
||||
|
||||
Operates on a 4x4 matrix and performs: D = A x B + C.
|
||||
|
||||

|
||||
|
||||
### GPU Multi-Process Scheduling
|
||||
|
||||
- Timeslice scheduling: single process throughput optimization.
|
||||
- Multi process service: multi-process throughput optimization.
|
||||
|
||||
How about multi-process time slicing:
|
||||
|
||||

|
||||
|
||||
Volta introduces the multi-process services:
|
||||
|
||||

|
||||
|
||||
|
BIN
YaeBlog/source/drafts/hpc-2025-cuda/image-20250424192311202.webp
(Stored with Git LFS)
Normal file
BIN
YaeBlog/source/drafts/hpc-2025-cuda/image-20250424192311202.webp
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
YaeBlog/source/drafts/hpc-2025-cuda/image-20250424192825010.webp
(Stored with Git LFS)
Normal file
BIN
YaeBlog/source/drafts/hpc-2025-cuda/image-20250424192825010.webp
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
YaeBlog/source/drafts/hpc-2025-cuda/image-20250424193125125.webp
(Stored with Git LFS)
Normal file
BIN
YaeBlog/source/drafts/hpc-2025-cuda/image-20250424193125125.webp
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
YaeBlog/source/drafts/hpc-2025-cuda/image-20250424195111341.webp
(Stored with Git LFS)
Normal file
BIN
YaeBlog/source/drafts/hpc-2025-cuda/image-20250424195111341.webp
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
YaeBlog/source/drafts/hpc-2025-cuda/image-20250424195221886.webp
(Stored with Git LFS)
Normal file
BIN
YaeBlog/source/drafts/hpc-2025-cuda/image-20250424195221886.webp
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
YaeBlog/source/drafts/hpc-2025-cuda/image-20250424200022880.webp
(Stored with Git LFS)
Normal file
BIN
YaeBlog/source/drafts/hpc-2025-cuda/image-20250424200022880.webp
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
YaeBlog/source/drafts/hpc-2025-cuda/image-20250424200330783.webp
(Stored with Git LFS)
Normal file
BIN
YaeBlog/source/drafts/hpc-2025-cuda/image-20250424200330783.webp
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
YaeBlog/source/drafts/hpc-2025-cuda/image-20250508183446257.webp
(Stored with Git LFS)
Normal file
BIN
YaeBlog/source/drafts/hpc-2025-cuda/image-20250508183446257.webp
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
YaeBlog/source/drafts/hpc-2025-cuda/image-20250508183528381.webp
(Stored with Git LFS)
Normal file
BIN
YaeBlog/source/drafts/hpc-2025-cuda/image-20250508183528381.webp
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
YaeBlog/source/drafts/hpc-2025-cuda/image-20250508183724162.webp
(Stored with Git LFS)
Normal file
BIN
YaeBlog/source/drafts/hpc-2025-cuda/image-20250508183724162.webp
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
YaeBlog/source/drafts/hpc-2025-cuda/image-20250508183931464.webp
(Stored with Git LFS)
Normal file
BIN
YaeBlog/source/drafts/hpc-2025-cuda/image-20250508183931464.webp
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
YaeBlog/source/drafts/hpc-2025-cuda/image-20250508184155087.webp
(Stored with Git LFS)
Normal file
BIN
YaeBlog/source/drafts/hpc-2025-cuda/image-20250508184155087.webp
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
YaeBlog/source/drafts/hpc-2025-cuda/image-20250508184455215.webp
(Stored with Git LFS)
Normal file
BIN
YaeBlog/source/drafts/hpc-2025-cuda/image-20250508184455215.webp
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
YaeBlog/source/drafts/hpc-2025-cuda/image-20250508185236920.webp
(Stored with Git LFS)
Normal file
BIN
YaeBlog/source/drafts/hpc-2025-cuda/image-20250508185236920.webp
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
YaeBlog/source/drafts/hpc-2025-cuda/image-20250508185812302.webp
(Stored with Git LFS)
Normal file
BIN
YaeBlog/source/drafts/hpc-2025-cuda/image-20250508185812302.webp
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
YaeBlog/source/drafts/hpc-2025-cuda/image-20250508193756274.webp
(Stored with Git LFS)
Normal file
BIN
YaeBlog/source/drafts/hpc-2025-cuda/image-20250508193756274.webp
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
YaeBlog/source/drafts/hpc-2025-cuda/image-20250508194350572.webp
(Stored with Git LFS)
Normal file
BIN
YaeBlog/source/drafts/hpc-2025-cuda/image-20250508194350572.webp
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
YaeBlog/source/drafts/hpc-2025-cuda/image-20250508195840957.webp
(Stored with Git LFS)
Normal file
BIN
YaeBlog/source/drafts/hpc-2025-cuda/image-20250508195840957.webp
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
YaeBlog/source/drafts/hpc-2025-cuda/image-20250508195938546.webp
(Stored with Git LFS)
Normal file
BIN
YaeBlog/source/drafts/hpc-2025-cuda/image-20250508195938546.webp
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
YaeBlog/source/drafts/hpc-2025-cuda/image-20250508200041910.webp
(Stored with Git LFS)
Normal file
BIN
YaeBlog/source/drafts/hpc-2025-cuda/image-20250508200041910.webp
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
YaeBlog/source/drafts/hpc-2025-cuda/image-20250515183524043.webp
(Stored with Git LFS)
Normal file
BIN
YaeBlog/source/drafts/hpc-2025-cuda/image-20250515183524043.webp
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
YaeBlog/source/drafts/hpc-2025-cuda/image-20250515184225475.webp
(Stored with Git LFS)
Normal file
BIN
YaeBlog/source/drafts/hpc-2025-cuda/image-20250515184225475.webp
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
YaeBlog/source/drafts/hpc-2025-cuda/image-20250515184621914.webp
(Stored with Git LFS)
Normal file
BIN
YaeBlog/source/drafts/hpc-2025-cuda/image-20250515184621914.webp
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
YaeBlog/source/drafts/hpc-2025-cuda/image-20250515184714663.webp
(Stored with Git LFS)
Normal file
BIN
YaeBlog/source/drafts/hpc-2025-cuda/image-20250515184714663.webp
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
YaeBlog/source/drafts/hpc-2025-cuda/image-20250515185019590.webp
(Stored with Git LFS)
Normal file
BIN
YaeBlog/source/drafts/hpc-2025-cuda/image-20250515185019590.webp
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
YaeBlog/source/drafts/hpc-2025-cuda/image-20250515185034758.webp
(Stored with Git LFS)
Normal file
BIN
YaeBlog/source/drafts/hpc-2025-cuda/image-20250515185034758.webp
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
YaeBlog/source/drafts/hpc-2025-cuda/image-20250515185212184.webp
(Stored with Git LFS)
Normal file
BIN
YaeBlog/source/drafts/hpc-2025-cuda/image-20250515185212184.webp
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
YaeBlog/source/drafts/hpc-2025-cuda/image-20250515185801775.webp
(Stored with Git LFS)
Normal file
BIN
YaeBlog/source/drafts/hpc-2025-cuda/image-20250515185801775.webp
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
YaeBlog/source/drafts/hpc-2025-cuda/image-20250515190244112.webp
(Stored with Git LFS)
Normal file
BIN
YaeBlog/source/drafts/hpc-2025-cuda/image-20250515190244112.webp
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
YaeBlog/source/drafts/hpc-2025-cuda/image-20250515190507199.webp
(Stored with Git LFS)
Normal file
BIN
YaeBlog/source/drafts/hpc-2025-cuda/image-20250515190507199.webp
(Stored with Git LFS)
Normal file
Binary file not shown.
Binary file not shown.
BIN
YaeBlog/source/drafts/hpc-2025-cuda/image-20250515191142384.webp
(Stored with Git LFS)
Normal file
BIN
YaeBlog/source/drafts/hpc-2025-cuda/image-20250515191142384.webp
(Stored with Git LFS)
Normal file
Binary file not shown.
@@ -0,0 +1,10 @@
|
||||
---
|
||||
title: High Performance Computing 25 SP Quantum Computing
|
||||
date: 2025-06-12T19:26:24.6668760+08:00
|
||||
tags:
|
||||
- 高性能计算
|
||||
- 学习资料
|
||||
---
|
||||
|
||||
|
||||
<!--more-->
|
239
YaeBlog/source/drafts/hpc-2025-non-stored-program-computing.md
Normal file
239
YaeBlog/source/drafts/hpc-2025-non-stored-program-computing.md
Normal file
@@ -0,0 +1,239 @@
|
||||
---
|
||||
title: High Performance Computing 2025 SP Non Stored Program Computing
|
||||
date: 2025-05-29T18:29:28.6155560+08:00
|
||||
tags:
|
||||
- 高性能计算
|
||||
- 学习资料
|
||||
---
|
||||
|
||||
No Von Neumann Machines.
|
||||
|
||||
<!--more-->
|
||||
|
||||
## Application Specified Integrated Circuits
|
||||
|
||||
As known as **ASIC**, these hardwares can work along and are not von Neumann machines.
|
||||
|
||||
No stored program concept:
|
||||
|
||||
- Input data come in
|
||||
- Pass through all circuit gates quickly
|
||||
- Generate output results immediately
|
||||
|
||||
Advantages: performance is better.
|
||||
|
||||
Disadvantages: reusability is worse.
|
||||
|
||||
> The CPU and GPU are special kinds of ASIC.
|
||||
|
||||
Why we need ASIC in computing:
|
||||
|
||||
- Alternatives to the Moore'a law.
|
||||
- High capacity and high speed.
|
||||
|
||||

|
||||
|
||||
### Full Custom ASICs
|
||||
|
||||
All mask layers are customized in a full-custom ASICs.
|
||||
|
||||
The full-custom ASICs always can offer the highest performance and lowest part cost (smallest die size) for a given design.
|
||||
|
||||
A typical example of full-custom ASICs is the CPU.
|
||||
|
||||
The advantages and disadvantages of full-custom ASICs is shown below.
|
||||
|
||||
| Advantages | Disadvantages |
|
||||
| ------------------------------------------------------------ | -------------------------------------------------------- |
|
||||
| Reducing the area | The design process takes a longer time |
|
||||
| Enhancing the performance | Having more complexity in computer-aided design tool |
|
||||
| Better ability of integrating with other analog components and other pre-designed components | Requiring higher investment and skilled human resources. |
|
||||
|
||||
### Semi Custom ASICs
|
||||
|
||||
All the logical cell are predesigned and some or all of the mask layer is customized.
|
||||
|
||||
There are two types of semi-custom ASICs:
|
||||
|
||||
- Standard cell based ASICs
|
||||
- Gate-array based ASICs.
|
||||
|
||||
The Standard cell based ASICs is also called as **Cell-based ASIC(CBIC)**.
|
||||
|
||||

|
||||
|
||||
> The *gate* is used a unit to measure the ability of semiconductor to store logical elements.
|
||||
|
||||
The semi-custom ASICs is developed as:
|
||||
|
||||
- Programmable Logic Array(PLA)
|
||||
- Complex Programmable Logical Device(CPLD)
|
||||
- Programmable Array Logical
|
||||
- Field Programing Gate Array(FPGA)
|
||||
|
||||
#### Programmable Logical Device
|
||||
|
||||
An integrated circuit that can be programmed/reprogrammed with a digital logical of a curtain level.
|
||||
|
||||
The basic idea of PLD is an array of **AND** gates and an array of **OR** gates. Each input feeds both a non-inverting buffer and an inverting buffer to produce the true and inverted forms of each variable. The AND outputs are called the product lines. Each product line is connected to one of the inputs of each OR gate.
|
||||
|
||||
Depending on the structure, the standard PLD can be divided into:
|
||||
|
||||
- Read Only Memory(ROM): A fixed array of AND gates and a programmable array of OR gates.
|
||||
- Programmable Array Logic(PAL): A programmable array of AND gates feeding a fixed array of OR gates.
|
||||
- Programmable Logic Array(PLA): A programmable array of AND gates feeding a programmable of OR gates.
|
||||
- Complex Programmable Logic Device(CPLD) and Field Programmable Gate Array(FPGA): complex enough to be called as *architecture*.
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
## Field Programming Gate Array
|
||||
|
||||
> General speaking, all semiconductor can be considered as a special kind of ASIC. But in practice, we always refer the circuit with a special function as ASIC, a circuit that can change the function as FPGA.
|
||||
|
||||

|
||||
|
||||
### FPGA Architecture
|
||||
|
||||

|
||||
|
||||
#### Configurable Logic Block(CLB) Architecture
|
||||
|
||||
The CLB consists of:
|
||||
|
||||
- Look-up Table(LUT): implements the entries of a logic functions truth table.
|
||||
|
||||
And some FPGAs can use the LUTs to implement small random access memory(RAM).
|
||||
|
||||
- Carry and Control Logic: Implements fast arithmetic operation(adders/subtractors).
|
||||
|
||||
- Memory Elements: configures flip flops/latches (programmable clock edges, set, reset and clock enable). These memory elements usually can be configured as shift-registers.
|
||||
|
||||
##### Configuring LUTs
|
||||
|
||||
LUT is a ram with data width of 1 bit and the content is programmed at power up. Internal signals connect to control signals of MUXs to select a values of the truth tables for any given input signals.
|
||||
|
||||
The below figure shows LUT working:
|
||||
|
||||

|
||||
|
||||
The configuration memory holds the output of truth table entries, so that when the FPGA is restarting it will run with the same *program*.
|
||||
|
||||
And as the truth table entries are just bits, the program of FPGA is called as **BITSTREAM**, we download a bitstream to an FPGA and all LUTs will be configured using the BITSTREAM to implement the boolean logic.
|
||||
|
||||
##### LUT Based Ram
|
||||
|
||||
Let the input signal as address, the LUT will be configured as a RAM. Normally, LUT mode performs read operations, the address decoders can generate clock signal to latches for writing operation.
|
||||
|
||||

|
||||
|
||||
#### Routing Architecture
|
||||
|
||||
The logic blocks are connected to each though programmable routing network. And the routing network provides routing connections among logic blocks and I/O blocks to complete a user-designed circuit.
|
||||
|
||||
Horizontal and vertical mesh or wire segments interconnection by programmable switches called programmable interconnect points(PIPs).
|
||||
|
||||

|
||||
|
||||
These PIPs are implemented using a transmission gate controlled by a memory bits from the configuration memory.
|
||||
|
||||
Several types of PIPs are used in the FPGA:
|
||||
|
||||
- Cross-point: connects vertical or horizontal wire segments allowing turns.
|
||||
- Breakpoint: connects or isolates 2 wire segments.
|
||||
- Decoded MUX: groups of cross-points connected to a single output configured by n configuration bits.
|
||||
- Non-decoded MUX: n wire segments each with a configuration bit.
|
||||
- Compound cross-point: 6 breakpoint PIPs and can isolate two isolated signal nets.
|
||||
|
||||

|
||||
|
||||
#### Input/Output Architecture
|
||||
|
||||
The I/O pad and surrounding supporting logical and circuitry are referred as input/input cell.
|
||||
|
||||
The programmable Input/Output cells consists of three parts:
|
||||
|
||||
- Bi-directional buffers
|
||||
- Routing resources.
|
||||
- Programmable I/O voltage and current levels.
|
||||
|
||||

|
||||
|
||||
#### Fine-grained and Coarse-grained Architecture
|
||||
|
||||
The fine-grained architecture:
|
||||
|
||||
- Each logic block can implement a very simple function.
|
||||
- Very efficient in implementing systolic algorithms.
|
||||
- Has a large number of interconnects per logic block than the functionality they offer.
|
||||
|
||||
The coarse-grained architecture:
|
||||
|
||||
- Each logic block is relatively packed with more logic.
|
||||
- Has their logic blocks packed with more functionality.
|
||||
- Has fewer interconnections which leading to reduce the propagating delays encountered.
|
||||
|
||||
#### Interconnect Devices
|
||||
|
||||
FPGAs are based on an array of logic modules and uncommitted wires to route signal.
|
||||
|
||||
Three types of interconnected devices have been commonly used to connect there wires:
|
||||
|
||||
- Static random access memory (SRAM) based
|
||||
- Anti-fuse based
|
||||
- EEPROM based
|
||||
|
||||
### FPGA Design Flow
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
The FPGA configuration techniques contains:
|
||||
|
||||
- Full configuration and read back.
|
||||
- Partial re-configuration and read back.
|
||||
- Compressed configuration.
|
||||
|
||||
Based on the partially reconfiguration, the runtime reconfiguration is development. The area to be reconfigured is changed based on run-time.
|
||||
|
||||
#### Hardware Description Languages(HDL)
|
||||
|
||||
There are three languages targeting FPGAs:
|
||||
|
||||
- VHDL: VHSIC Hardware Description Language.
|
||||
- Verilog
|
||||
- OpenCL
|
||||
|
||||
The first two language are typical HDL:
|
||||
|
||||
| Verilog | VHDL |
|
||||
| -------------------------------------- | ------------------------------- |
|
||||
| Has fixed data types. | Has abstract data types. |
|
||||
| Relatively easy to learn. | Relatively difficult to learn. |
|
||||
| Good gate level timing. | Poor level gate timing. |
|
||||
| Interpreted constructs. | Compiled constructs. |
|
||||
| Limited design reusability. | Good design reusability. |
|
||||
| Doesn't support structure replication. | Supports structure replication. |
|
||||
| Limited design management. | Good design management. |
|
||||
|
||||
The OpenCL is not an traditional hardare description language. And OpenCL needs to turn the thread parallelism into hardware parallelism, called **pipeline parallelism**.
|
||||
|
||||
The follow figure shows how the OpenCL-FPGA compiler turns an vector adding function into the circuit.
|
||||
|
||||

|
||||
|
||||
The compiler generates three stages for this function:
|
||||
|
||||
1. In the first stage, two loading units are used.
|
||||
2. In the second stage, one adding unit is used.
|
||||
3. In the third stage, one storing unit is used.
|
||||
|
||||
Once cycle, the thread `N` is clocked in the first stage, loading values from the array meanwhile, the thread `N - 1` is in the second stage, adding values from the array and the thread `N - 2` is in the third stage, storing value into the target array.
|
||||
|
||||
So different from the CPU and GPU, the OpenCL on the FPGA has two levels of parallelism:
|
||||
|
||||
- Pipelining
|
||||
- Replication of the kernels and having them run concurrently.
|
||||
|
BIN
YaeBlog/source/drafts/hpc-2025-non-stored-program-computing/image-20250605185212740.webp
(Stored with Git LFS)
Normal file
BIN
YaeBlog/source/drafts/hpc-2025-non-stored-program-computing/image-20250605185212740.webp
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
YaeBlog/source/drafts/hpc-2025-non-stored-program-computing/image-20250612184120333.webp
(Stored with Git LFS)
Normal file
BIN
YaeBlog/source/drafts/hpc-2025-non-stored-program-computing/image-20250612184120333.webp
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
YaeBlog/source/drafts/hpc-2025-non-stored-program-computing/image-20250815093113115.png
(Stored with Git LFS)
Normal file
BIN
YaeBlog/source/drafts/hpc-2025-non-stored-program-computing/image-20250815093113115.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
YaeBlog/source/drafts/hpc-2025-non-stored-program-computing/image-20250817183832472.png
(Stored with Git LFS)
Normal file
BIN
YaeBlog/source/drafts/hpc-2025-non-stored-program-computing/image-20250817183832472.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
YaeBlog/source/drafts/hpc-2025-non-stored-program-computing/image-20250817184419856.png
(Stored with Git LFS)
Normal file
BIN
YaeBlog/source/drafts/hpc-2025-non-stored-program-computing/image-20250817184419856.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
YaeBlog/source/drafts/hpc-2025-non-stored-program-computing/image-20250817185111521.png
(Stored with Git LFS)
Normal file
BIN
YaeBlog/source/drafts/hpc-2025-non-stored-program-computing/image-20250817185111521.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
YaeBlog/source/drafts/hpc-2025-non-stored-program-computing/image-20250817185859510.png
(Stored with Git LFS)
Normal file
BIN
YaeBlog/source/drafts/hpc-2025-non-stored-program-computing/image-20250817185859510.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
YaeBlog/source/drafts/hpc-2025-non-stored-program-computing/image-20250817192006784.png
(Stored with Git LFS)
Normal file
BIN
YaeBlog/source/drafts/hpc-2025-non-stored-program-computing/image-20250817192006784.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
YaeBlog/source/drafts/hpc-2025-non-stored-program-computing/image-20250817194355228.png
(Stored with Git LFS)
Normal file
BIN
YaeBlog/source/drafts/hpc-2025-non-stored-program-computing/image-20250817194355228.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
YaeBlog/source/drafts/hpc-2025-non-stored-program-computing/image-20250817195139631.png
(Stored with Git LFS)
Normal file
BIN
YaeBlog/source/drafts/hpc-2025-non-stored-program-computing/image-20250817195139631.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
YaeBlog/source/drafts/hpc-2025-non-stored-program-computing/image-20250817195714935.png
(Stored with Git LFS)
Normal file
BIN
YaeBlog/source/drafts/hpc-2025-non-stored-program-computing/image-20250817195714935.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
YaeBlog/source/drafts/hpc-2025-non-stored-program-computing/image-20250817200350750.png
(Stored with Git LFS)
Normal file
BIN
YaeBlog/source/drafts/hpc-2025-non-stored-program-computing/image-20250817200350750.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
YaeBlog/source/drafts/hpc-2025-non-stored-program-computing/image-20250829210329225.png
(Stored with Git LFS)
Normal file
BIN
YaeBlog/source/drafts/hpc-2025-non-stored-program-computing/image-20250829210329225.png
(Stored with Git LFS)
Normal file
Binary file not shown.
99
YaeBlog/source/drafts/hpc-2025-opencl.md
Normal file
99
YaeBlog/source/drafts/hpc-2025-opencl.md
Normal file
@@ -0,0 +1,99 @@
|
||||
---
|
||||
title: High Performance Computing 2025 SP OpenCL Programming
|
||||
date: 2025-05-29T18:29:14.8444660+08:00
|
||||
tags:
|
||||
- 高性能计算
|
||||
- 学习资料
|
||||
---
|
||||
|
||||
Open Computing Language.
|
||||
|
||||
<!--more-->
|
||||
|
||||
OpenCL is Open Computing Language.
|
||||
|
||||
- Open, royalty-free standard C-language extension.
|
||||
- For parallel programming of heterogeneous systems using GPUs, CPUs , CBE, DSP and other processors including embedded mobile devices.
|
||||
- Managed by Khronos Group.
|
||||
|
||||

|
||||
|
||||
### Anatomy of OpenCL
|
||||
|
||||
- Platform Layer APi
|
||||
- Runtime Api
|
||||
- Language Specification
|
||||
|
||||
### Compilation Model
|
||||
|
||||
OpenCL uses dynamic/runtime compilation model like OpenGL.
|
||||
|
||||
1. The code is compiled to an IR.
|
||||
2. The IR is compiled to a machine code for execution.
|
||||
|
||||
And in dynamic compilation, *step 1* is done usually once and the IR is stored. The app loads the IR and performs *step 2* during the app runtime.
|
||||
|
||||
### Execution Model
|
||||
|
||||
OpenCL program is divided into
|
||||
|
||||
- Kernel: basic unit of executable code.
|
||||
- Host: collection of compute kernels and internal functions.
|
||||
|
||||
The host program invokes a kernel over an index space called an **NDRange**.
|
||||
|
||||
NDRange is *N-Dimensional Range*, and can be a 1, 2, 3-dimensional space.
|
||||
|
||||
A single kernel instance at a point of this index space is called **work item**. Work items are further grouped into **work groups**.
|
||||
|
||||
### OpenCL Memory Model
|
||||
|
||||

|
||||
|
||||
Multiple distinct address spaces: Address can be collapsed depending on the device's memory subsystem.
|
||||
|
||||
Address space:
|
||||
|
||||
- Private: private to a work item.
|
||||
- Local: local to a work group.
|
||||
- Global: accessible by all work items in all work groups.
|
||||
- Constant: read only global memory.
|
||||
|
||||
> Comparison with CUDA:
|
||||
>
|
||||
> 
|
||||
|
||||
Memory region for host and kernel:
|
||||
|
||||

|
||||
|
||||
### Programming Model
|
||||
|
||||
#### Data Parallel Programming Model
|
||||
|
||||
1. Define N-Dimensional computation domain
|
||||
2. Work-items can be grouped together as *work group*.
|
||||
3. Execute multiple work-groups in parallel.
|
||||
|
||||
#### Task Parallel Programming Model
|
||||
|
||||
> Data parallel execution model must be implemented by all OpenCL computing devices, but task parallel programming is a choice for vendor.
|
||||
|
||||
Some computing devices such as CPUs can also execute task-parallel computing kernels.
|
||||
|
||||
- Executes as s single work item.
|
||||
- A computing kernel written in OpenCL.
|
||||
- A native function.
|
||||
|
||||
### OpenCL Framework
|
||||
|
||||

|
||||
|
||||
The basic OpenCL program structure:
|
||||
|
||||

|
||||
|
||||
**Contexts** are used to contain the manage the state of the *world*.
|
||||
|
||||
**Command-queue** coordinates execution of the kernels.
|
||||
|
BIN
YaeBlog/source/drafts/hpc-2025-opencl/image-20250529185915068.webp
(Stored with Git LFS)
Normal file
BIN
YaeBlog/source/drafts/hpc-2025-opencl/image-20250529185915068.webp
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
YaeBlog/source/drafts/hpc-2025-opencl/image-20250529191215424.webp
(Stored with Git LFS)
Normal file
BIN
YaeBlog/source/drafts/hpc-2025-opencl/image-20250529191215424.webp
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
YaeBlog/source/drafts/hpc-2025-opencl/image-20250529191414250.webp
(Stored with Git LFS)
Normal file
BIN
YaeBlog/source/drafts/hpc-2025-opencl/image-20250529191414250.webp
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
YaeBlog/source/drafts/hpc-2025-opencl/image-20250529191512490.webp
(Stored with Git LFS)
Normal file
BIN
YaeBlog/source/drafts/hpc-2025-opencl/image-20250529191512490.webp
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
YaeBlog/source/drafts/hpc-2025-opencl/image-20250529192022613.webp
(Stored with Git LFS)
Normal file
BIN
YaeBlog/source/drafts/hpc-2025-opencl/image-20250529192022613.webp
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
YaeBlog/source/drafts/hpc-2025-opencl/image-20250529192056388.webp
(Stored with Git LFS)
Normal file
BIN
YaeBlog/source/drafts/hpc-2025-opencl/image-20250529192056388.webp
(Stored with Git LFS)
Normal file
Binary file not shown.
78
YaeBlog/source/drafts/hpc-2025-potpourri.md
Normal file
78
YaeBlog/source/drafts/hpc-2025-potpourri.md
Normal file
@@ -0,0 +1,78 @@
|
||||
---
|
||||
title: High Performance Computing 25 SP Potpourri
|
||||
date: 2025-06-12T18:45:49.2698190+08:00
|
||||
tags:
|
||||
- 高性能计算
|
||||
- 学习资料
|
||||
---
|
||||
|
||||
Potpourri has a good taste.
|
||||
|
||||
<!--more-->
|
||||
|
||||
## Heterogeneous System Architecture
|
||||
|
||||

|
||||
|
||||
The goals of the HSA:
|
||||
|
||||
- Enable power efficient performance.
|
||||
- Improve programmability of heterogeneous processors.
|
||||
- Increase the portability of code across processors and platforms.
|
||||
- Increase the pervasiveness of heterogeneous solutions.
|
||||
|
||||
### The Runtime Stack
|
||||
|
||||

|
||||
|
||||
## Accelerated Processing Unit
|
||||
|
||||
A processor that combines the CPU and the GPU elements into a single architecture.
|
||||
|
||||

|
||||
|
||||
## Intel Xeon Phi
|
||||
|
||||
The goal:
|
||||
|
||||
- Leverage X86 architecture and existing X86 programming models.
|
||||
- Dedicate much of the silicon to floating point ops.
|
||||
- Cache coherent.
|
||||
- Increase floating-point throughput.
|
||||
- Strip expensive features.
|
||||
|
||||
The reality:
|
||||
|
||||
- 10s of x86-based cores.
|
||||
- Very high-bandwidth local GDDR5 memory.
|
||||
- The card runs a modified embedded Linux.
|
||||
|
||||
## Deep Learning: Deep Neural Networks
|
||||
|
||||
The network can used as a computer.
|
||||
|
||||
## Tensor Processing Unit
|
||||
|
||||
A custom ASIC for the phase of Neural Networks (AI accelerator).
|
||||
|
||||
### TPUv1 Architecture
|
||||
|
||||

|
||||
|
||||
### TPUv2 Architecture
|
||||
|
||||

|
||||
|
||||
Advantages of TPU:
|
||||
|
||||
- Allows to make predications very quickly and respond within fraction of a second.
|
||||
- Accelerate performance of linear computation, key of machine learning applications.
|
||||
- Minimize the time to accuracy when you train large and complex network models.
|
||||
|
||||
Disadvantages of TPU:
|
||||
|
||||
- Linear algebra that requires heavy branching or are not computed on the basis of element wise algebra.
|
||||
- Non-dominated matrix multiplication is not likely to perform well on TPUs.
|
||||
- Workloads that access memory using sparse technique.
|
||||
- Workloads that use highly precise arithmetic operations.
|
||||
|
BIN
YaeBlog/source/drafts/hpc-2025-potpourri/image-20250612185019968.webp
(Stored with Git LFS)
Normal file
BIN
YaeBlog/source/drafts/hpc-2025-potpourri/image-20250612185019968.webp
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
YaeBlog/source/drafts/hpc-2025-potpourri/image-20250612185221643.webp
(Stored with Git LFS)
Normal file
BIN
YaeBlog/source/drafts/hpc-2025-potpourri/image-20250612185221643.webp
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
YaeBlog/source/drafts/hpc-2025-potpourri/image-20250612185743675.webp
(Stored with Git LFS)
Normal file
BIN
YaeBlog/source/drafts/hpc-2025-potpourri/image-20250612185743675.webp
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
YaeBlog/source/drafts/hpc-2025-potpourri/image-20250612191035632.webp
(Stored with Git LFS)
Normal file
BIN
YaeBlog/source/drafts/hpc-2025-potpourri/image-20250612191035632.webp
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
YaeBlog/source/drafts/hpc-2025-potpourri/image-20250612191118473.webp
(Stored with Git LFS)
Normal file
BIN
YaeBlog/source/drafts/hpc-2025-potpourri/image-20250612191118473.webp
(Stored with Git LFS)
Normal file
Binary file not shown.
41
YaeBlog/source/drafts/hpc-2025-program-cuda.md
Normal file
41
YaeBlog/source/drafts/hpc-2025-program-cuda.md
Normal file
@@ -0,0 +1,41 @@
|
||||
---
|
||||
title: High Performance Computing 2025 SP Programming CUDA
|
||||
date: 2025-05-15T19:13:48.8893010+08:00
|
||||
tags:
|
||||
- 高性能计算
|
||||
- 学习资料
|
||||
---
|
||||
|
||||
Compute Unified Device Architecture
|
||||
|
||||
<!--more-->
|
||||
|
||||
## CUDA
|
||||
|
||||
General purpose programming model:
|
||||
|
||||
- Use kicks off batches of threads on the GPU.
|
||||
|
||||

|
||||
|
||||
The compiling C with CUDA applications:
|
||||
|
||||

|
||||
|
||||
### CUDA APIs
|
||||
|
||||
Areas:
|
||||
|
||||
- Device management
|
||||
- Context management
|
||||
- Memory management
|
||||
- Code module management
|
||||
- Execution control
|
||||
- Texture reference management
|
||||
- Interoperability with OpenGL and Direct3D
|
||||
|
||||
Two APIs:
|
||||
|
||||
- A low-level API called the CUDA driver API.
|
||||
- A higher-level API called the C runtime for CUDA that is implemented on top of the CUDA driver API.
|
||||
|
BIN
YaeBlog/source/drafts/hpc-2025-program-cuda/image-20250515195739382.webp
(Stored with Git LFS)
Normal file
BIN
YaeBlog/source/drafts/hpc-2025-program-cuda/image-20250515195739382.webp
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
YaeBlog/source/drafts/hpc-2025-program-cuda/image-20250515195907764.webp
(Stored with Git LFS)
Normal file
BIN
YaeBlog/source/drafts/hpc-2025-program-cuda/image-20250515195907764.webp
(Stored with Git LFS)
Normal file
Binary file not shown.
@@ -1,10 +1,12 @@
|
||||
---
|
||||
title: 2021年终总结
|
||||
date: 2022-01-12 16:27:19
|
||||
date: 2022-01-12T16:27:19.0000000
|
||||
tags:
|
||||
- 随笔
|
||||
- 杂谈
|
||||
- 年终总结
|
||||
---
|
||||
|
||||
|
||||
2021年已经过去,2022年已经来临。每每一年开始的时候,我都会展开一张纸或者新建一个文档,思量着又是一年时光,也该同诸大杂志一般,写几句意味深长的话语,怀念过去的时光,也祝福未来的自己。可往往脑海中已是三万字的长篇,落在笔头却又是一个字都没有了。
|
||||
如今跨年的时候已经过去,朋友圈中已经不见文案的踪影,我也该重新提笔,细说自己2021年中做过的种种。
|
||||
|
||||
@@ -22,7 +24,7 @@ tags:
|
||||
在前12年的学生生涯中,我们都在期待着这一次的暑假,以为在这个没有作业的假期里,我们就可以充分的享受人间的美好。可是,当时我们不知道,这人间的烦恼,可不止作业这一种,无论是突如其来的疫情导致开学延期,还是等待录取时的不安。
|
||||
虽说在暑假时,拥有了自己的笔记本电脑,可是在高中三年屯下的游戏还是没有玩几个,看来我也是“喜加一”的受害者。虽然在高考后入坑了原神,但是假期间我并没有太过投入的玩。
|
||||
暑假下定决心要好好的学一学,可是看着我gitee上暑假期间那稀疏的提交,我就知道我又摸了一个暑假的鱼。
|
||||

|
||||

|
||||
即使我想写的很多项目都没有被扎实的推进下来,但是学习的一些的C语言还是让我受益匪浅。
|
||||
现在看来,这个假期真是,**学也没有学好,耍也没有耍好**的典型。
|
||||
|
||||
|
BIN
YaeBlog/source/posts/2021-final/1.png
(Stored with Git LFS)
BIN
YaeBlog/source/posts/2021-final/1.png
(Stored with Git LFS)
Binary file not shown.
BIN
YaeBlog/source/posts/2021-final/1.webp
(Stored with Git LFS)
Normal file
BIN
YaeBlog/source/posts/2021-final/1.webp
(Stored with Git LFS)
Normal file
Binary file not shown.
@@ -1,11 +1,13 @@
|
||||
---
|
||||
title: 2022年终总结
|
||||
date: 2022-12-30T14:58:12.0000000
|
||||
tags:
|
||||
- 随笔
|
||||
date: 2022-12-30 14:58:12
|
||||
- 杂谈
|
||||
- 年终总结
|
||||
---
|
||||
|
||||
|
||||
|
||||
2022是困难的一年。我们需要为2023年做好准备。
|
||||
|
||||
<!--more-->
|
||||
@@ -56,11 +58,11 @@ date: 2022-12-30 14:58:12
|
||||
|
||||
小小的总结一下:2022年可以算得上是一事无成的一年,还搞砸了不少的事情。在写代码上进展有限,成绩上大幅倒退,说好的六级英语和大学物理竞赛都没有参加,在年末应对疫情进展的时候更是把“不知所措”这个成语诠释的淋漓尽致。
|
||||
|
||||

|
||||

|
||||
|
||||
关于今年的人际交往和社会关系,我愿意用QQ2022年年终总结中的一张截屏来总结,这张图片透漏出一种无可救药的悲伤。
|
||||
|
||||

|
||||

|
||||
|
||||
## 展望
|
||||
|
||||
|
BIN
YaeBlog/source/posts/2022-final/2022-12-30-14-26-19-QQ_Image_1672381538441.jpg
(Stored with Git LFS)
BIN
YaeBlog/source/posts/2022-final/2022-12-30-14-26-19-QQ_Image_1672381538441.jpg
(Stored with Git LFS)
Binary file not shown.
BIN
YaeBlog/source/posts/2022-final/2022-12-30-14-26-19-QQ_Image_1672381538441.webp
(Stored with Git LFS)
Normal file
BIN
YaeBlog/source/posts/2022-final/2022-12-30-14-26-19-QQ_Image_1672381538441.webp
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
YaeBlog/source/posts/2022-final/2022-12-30-14-28-12-QQ_Image_1672381543836.jpg
(Stored with Git LFS)
BIN
YaeBlog/source/posts/2022-final/2022-12-30-14-28-12-QQ_Image_1672381543836.jpg
(Stored with Git LFS)
Binary file not shown.
BIN
YaeBlog/source/posts/2022-final/2022-12-30-14-28-12-QQ_Image_1672381543836.webp
(Stored with Git LFS)
Normal file
BIN
YaeBlog/source/posts/2022-final/2022-12-30-14-28-12-QQ_Image_1672381543836.webp
(Stored with Git LFS)
Normal file
Binary file not shown.
@@ -1,11 +1,11 @@
|
||||
---
|
||||
title: 2022年暑假碎碎念
|
||||
date: 2022-08-22T15:39:13.0000000
|
||||
tags:
|
||||
- 随笔
|
||||
typora-root-url: 2022-summer-vacation
|
||||
date: 2022-08-22 15:39:13
|
||||
- 杂谈
|
||||
---
|
||||
|
||||
|
||||
在8个月的漫长寒假的最后两个月,~~也就是俗称的暑假中~~,我都干了些什么?
|
||||
|
||||
<!--more-->
|
||||
@@ -32,7 +32,7 @@ date: 2022-08-22 15:39:13
|
||||
- 下定决定要参加下一学期的物理竞赛,但是在听了讲座之后直接决定开学再开始学习,~~我知道我在家没法学习,俗称开摆~~
|
||||
- 又捡起了`Blender`,并在[Github](https://github.com/tanjian1998/bupt_minecraft)上找到了伟大的前辈们在`Minecraft`里复刻的老校区,希望能用`Blender`渲染几张图当作桌面。
|
||||
|
||||

|
||||

|
||||
|
||||
> 在此感谢所有为此付出过汗水的前辈们,让我这个即将搬入老校区的萌新能提前一睹老校区的风采。
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user