C# 尾递归优化

何为尾递归

有时候我们使用递归来解决一些特定的问题,但是使用递归需要注意不要导致栈溢出,这是使用递归的一个常见问题,对于规模足够大的问题,使用递归必定会导致栈溢出。通常,我们可以通过尾递归进行优化,尾递归可以避免栈溢出的问题(暂且这样认为)。
尾递归并不是什么新奇的东西,理解起来很简单,对于递归,如果上层调用的返回结果不依赖子调用的结果,那么,这就是一个尾递归。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
///这是一个简单的尾递归例子
namespace tail
{
class Program
{
static void Main(string[] args)
{
var test=RetrieveData(100000000000000);
}

static long RetrieveData(long unit)
{
if (unit == 0)
return unit;
return RetrieveData(--unit);
}
}
}

等等,好像有什么地方不对

从上面的例子分析,代码依旧会导致栈溢出,不是吗?是的,聪明的你答对了。那为什么说尾递归可以避免栈溢出问题?当然,从刚才的结论看,这个问题提的并不准确,尾递归并不能避免栈溢出问题。
仔细想想,尾递归结构和循环结构是类似的,上面的尾递归可以写成:

1
2
3
4
5
6
7
//糟糕的例子,但是就这样吧
static long RetriveData(long unit)
{
for(long i=unit;i<=0;i--)
continue;
return 0;
}

请忽略这个蠢函数什么都不做的事实。回到问题上,循环结构并不会导致栈溢出,基于这种结构上的对应,编译器可以对尾递归进行优化,重复使用栈上的递归函数,而不是每次调用都进行压栈操作,以此来避免栈溢出的问题。所以,完善的结论是:通过编译器优化,尾递归可以避免栈溢出的问题。

那么C#呢?

不幸的是,对于微软的 C# Compiler, 只有 x64 Release 才有尾递归优化。作为对比,我们分析其与 x86 Release 下编译后的代码的区别,看看编译器是如何优化尾递归调用的。

分析两者的 IL 代码,可以发现它们并没有什么区别,所以,真正进行优化操作的是 JIT 编译器。
分析两者的汇编代码:

可以看出,在 ASM_x64 中,递归调用没有重复压栈,而是在栈内跳转,类似于循环结构,以此来避免栈溢出问题。

业务逻辑中的 Filter

Pipe and Filter

在一些应用的开发场景中,例如,大量的数据处理、需要对数据进行大量的转换和过滤,使用管道和Filter是一个很好的选择。对于这种应用场景,一般都需要处理业务足够灵活,并且足够健壮。想象一下,为了实现相应的过滤功能,使用大量的if或者switch case,日后维护这套代码的人估计也会抓狂的吧,单元测试编写起来,应该也够呛吧。

Filter来处理业务

Filter一般都被认为是用来处理数据的,其实,更近一步,用来”处理”业务,也不失为一种好办法。当然,这里的处理,并不是说在Filter中执行业务,考虑到业务都是一些耗时(但不绝对)的操作,在管道中处理业务是不对的,这样会堵塞整条管道。相反地,我们的业务依赖管道中的数据,我们希望在数据处理的过程中,由数据的处理来触发特定的业务,因此,我们定义一种BusinessFilter,其不对数据进行处理,而是作为一种窥探,“假装”自己是在进行数据处理,实际是根据数据的实际来通知外界做相应的业务而已。

灵活性

正如上图的结构,搭积木式的链式结构可以根据业务的调整进行对应的调整,不同的业务可以在通道中穿插。从另一个方面可以看出,我们设计Filter的时候,保持它的独立性是至关重要的,一个Filter完成一个工作,而且不受其他Filter影响是保持整个链式结构灵活的关键。

健壮性

一般来说,管道/Filter结构一旦确定,会做大调整的几率都不高。由于Filter的存在,所有的变动在改动之前就可以确定影响范围,而且可以限制调整带来的结构变动。

存在的问题

  • 几乎所有的Pipe/Filter结构都是线性结构,这种情况限制了async/await的应用
  • 如果一个Filter只接受一种数据类型,那么存在的数据转换会降低我们系统的性能,特别地,如果是C#,进行引用类型和值类型的转换,频繁的装箱拆箱也会降低我们系统的性能;为了解决这种问题,限制Filter可接受的数据类型,这样就不能随意连接Filter到所有数据类型,通用性降低。

工作台

寝室工作台几张

正式工作台

手工台

强袭

使用 IDE 编译调试 Mlt framework

Mlt framework 是一个开源跨平台的多媒体处理框架,使用模块化的设计,集成了大量的业界领先的视频处理框架,如ffmpeg,良好的设计,可以方便的集成自己的模块进去,利用它,你可以实现自己的 Adobe Premiere 等非线性多媒体编辑软件或者视频播放器,简单几句代码为视频添加炫酷的转场效果和滤镜。

由于跨平台,项目通过configure的方式来管理工程,对于学习来说多媒体框架来说,调试起来并不方便,我整理了CMake的脚本,可以通过cmake来生成我们熟悉的 Visual Studio 或者 Xcode
工程,方便调试。

在阅读下面文章之前,请确保你有一定的 MinGW 工具链使用经验。

下载源码

https://github.com/gandalfliang/mlt
分支:cmake

配置环境

对于 Windows 平台:

  1. cmake
  2. MinGW
  3. msys2
  4. Windows SDK

确保MinGW和msys2里的工具链路径在系统变量PATH中

Mac 平台:

  1. Xcode

Bootstrap

在工程跟目录,运行脚本(如果是 Windows 平台,从开发者命令行工具运行):

1
py bootstrap.py

如果一切顺利,工程文件将会在 build/win32 或者 build/mac 下。脚本运行要拉取ffmpeg的源码并configure,这是一个耗时的工作。在Windows平台下configure ffmpeg将是一个漫长的等待,没事的话,去喝杯咖啡,或者有空的时候在命令窗里敲一下enter键,可能会有收获哦。

完成后,你就可以愉快的调试这个框架了。

Debug Mlt Framework in VSCode

Debugging Mlt framework in VSCode

Crossing platform with built in tool chain

Coming up soon (maybe)

Gitsoler

Gitsoler 1.1 - Split View supported: https://marketplace.visualstudio.com/items?itemName=gandalfliang.gitsoler

【翻译】关于 WPF 透明窗口的内存占用

翻译自己的文章才是最骚的。。。 Origin Post


要实现一个透明的 WPF 窗口?
多么简单的一个任务啊!只要设置 AllowTransparency 和 WindowStyle,你可以在毫秒间完成这个任务。

AllowTransparency="True" WindowStyle="None" Background="Transparent"

正确吧?当然。
但是(你懂得),打开你的任务管理器看看,简单的任务通常会带来大量的内存占用,特别是4K分辨率的透明窗口。100+ MB的内存被浪费了,就只为了显示一个空白的透明窗口!这是不可接受的。一年前,如果你说:”谁关心内存呀,现在的内存条太便宜了”,你可能是对的。但是查查这一年内存条的价格走向,它们现在贵上天了。

WPF 透明窗口的有趣小真相

  • 内存占用随着窗口尺寸增大而增加
  • Win32 窗口没有这样的问题

等等,什么?窗口越大,内存消耗的更多?嗯。。。这看起来很熟悉嘛,就像一个Bitmap。知道现在,我们并不知道 WPF 是如何处理透明窗口的,但是这种症状显示它就好像直接将整个桌面作为一个位图,然后窗口用这张位图的重叠部分作为其背景来更新自己,让它看起来是“透明”的。多么聪明的做法呀。。。
在 WPF 刚刚发布的那些日子里,低分辨率的计算机屏幕占据主流位置,即使在今天,大多数的笔记本电脑依然带着一块1366*768分辨率的屏幕被推向市场(离谱吧)。让我们唾弃那些OEM厂商讲的毫无根据的废话并且思考一下运行在高分辨率下的程序的情况。

内存并不是免费的,不要浪费之

很显然,浪费100+MB的内存来显示一个4K的透明窗口是不可接受的,特别是和 Win32 窗口只占用10+MB的内存进行比较时。这差距让 WPF 看起来蠢透了。
抱怨已经够多了,想想对此我们能做什么呢?我可不想用C++和GDI将我的UI代码重写一遍,这太没效率并且也跟不上时代,况且,没人会为此“区区小事”去放弃他们漂亮、易于维护的Xaml UI代码。

使用 Win32 承载 WPF 内容

好吧,确实,没人愿意为了区区90MB内存去重写它们的UI。与使用C++重写UI所耗费的精力相比,这个内存的占用差距看起来是可以接受的(#笑脸)。但是请记住,我们一如既往的可以在 Win32 窗口中承载 WPF 的内容。
例如,我们想创建一个全屏、半透明背景带着非透明内容的对话框。为了规避 WPF 透明窗口的内存问题,我们可以使用 Win32 创建一个半透明的窗口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
DWORD Flags1 = WS_EX_LAYERED;
DWORD Flags2 = WS_POPUP;

HWND hWnd = CreateWindowEx(Flags1,szWindowClass, szTitle, Flags2,
CW_USEDEFAULT, 0, 3840,2160, nullptr, nullptr, hInstance, nullptr);

SetLayeredWindowAttributes(hWnd, RRR, (BYTE)125, LWA_ALPHA);
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);

case WM_ERASEBKGND:
RECT rect;
GetClientRect(hWnd, &rect);
FillRect((HDC)wParam, &rect, CreateSolidBrush(RGB(0, 0, 0)));
break;

通过启用 C++/CLI,我们可以直接访问 WPF 内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
namespace ManagedCode
{
using namespace System;
using namespace System::Windows;
using namespace System::Windows::Interop;
using namespace System::Windows::Media;

HWND GetHwnd(HWND parent, int x, int y, int width, int height) {
HwndSource^ source = gcnew HwndSource(
0, // class style
WS_VISIBLE | WS_CHILD, // style
0, // exstyle
x, y, width, height,
"hi", // NAME
IntPtr(parent) // parent window
);

UIElement^ page = gcnew ManagedContent::WPFContent();
source->RootVisual = page;
return (HWND)source->Handle.ToPointer();
}
}

最后

1
2
//managed content
ManagedCode::GetHwnd(hWnd, 0, 0, 200, 200);

由于 WPF 和 GDI 背后的技术不尽相同,还有更多的工作需要做来解决不可避免的透明通道问题,但是,为了方便,你始终可以使用 Popup 来实现你的目标。


Macbook Pro 2016 的键盘真垃圾。。。。。。

Light Weighted DropshadowEffect

Let’s create a light weighted wpf drop shadow effect, considering that the origin one performs badly in some special occasions.
As I mentioned before (check it out), the original WPF DropShadow Effect can cause severe preformance problem. Due to the “flaw” M$FT brought to the HLSL support for WPF, the Effect class that implements the visual effect creates and destroy GPU resource each frame, which is the worst thing you could do with GPU resources. So, what about implementing a custom shadow effect to avoid it? This sounds interesting.

– 春节补充

关于 WPF 透明窗口的内存占用

要实现一个透明的 WPF 窗口?
What an easy task! By setting AllowTransparency and WindowStyle, you could finish it in seconds.

AllowTransparency="True" WindowStyle="None" Background="Transparent"

Correct? Of course.

However (you know this is coming), look at your task manager, easy task comes with large memory consumption, especially for 4K transparent window. 100+ MB ram are wasted for just showing an empty, transparent window! That’s unacceptable. A year ago, you might be right saying “Who cares about RAM, they are cheap as hell”, but check out the price they’ve grown over this year, they are expensive as hell now.

Fun fact of WPF transparent window

  • RAM usage increase as window size enlarge
  • Win32 window has no such problem

Wait, what? The larger the window is, the more RAM it consumes? Hmmmmm… this looks familiar, just like a Bitmap. For now, we don’t know how WPF handles transparent window, but the symptom shows that it’s like using the whole screen as a bitmap and the window updating itself with portion of that bitmap, making it “transparent”. What a smart move…
Back in the days when WPF was first released, low screen resolution was the main stream. Even today, most laptops still are shipping with a monitor of 1366*768 (ridiculous, right?). Let’s despise the nonsense the OEM told us and think about program running in computers with higher screen resolution.

RAM is not free, do not waste it

Obviously, costing 100+ mb of ram for showing a transparent window in 4K is unacceptable, especially compared with Win32 transparent window, which costs only 10+ mb. The gap between them makes WPF look dump.
Enough complaining, what can we do about it? I don’t want to write UI code with GDI using C++, that’s inefficient and not modern, plus, no one would abandon their beautiful, easy to maintain xaml UI code for this.

Hosting WPF content in Win32 Window

Well, indeed, no one would rewrite their UI code for just about 90mb of RAM. Compared with the work needed to rewrite C++ UI code, the RAM consumption gap seems acceptable (#smile face). But please remember, we can always host WPF content in win32 window.
Let say, we want to create a full screen notification dialog with semi-transparent background and apaque notication content in the center. To avoid the WPF ram problem, we create a semi-transparent window using win32:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
DWORD Flags1 = WS_EX_LAYERED;
DWORD Flags2 = WS_POPUP;

HWND hWnd = CreateWindowEx(Flags1,szWindowClass, szTitle, Flags2,
CW_USEDEFAULT, 0, 3840,2160, nullptr, nullptr, hInstance, nullptr);

SetLayeredWindowAttributes(hWnd, RRR, (BYTE)125, LWA_ALPHA);
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);

case WM_ERASEBKGND:
RECT rect;
GetClientRect(hWnd, &rect);
FillRect((HDC)wParam, &rect, CreateSolidBrush(RGB(0, 0, 0)));
break;

By enabling C++/CLI, we can access WPF content directly

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
namespace ManagedCode
{
using namespace System;
using namespace System::Windows;
using namespace System::Windows::Interop;
using namespace System::Windows::Media;

HWND GetHwnd(HWND parent, int x, int y, int width, int height) {
HwndSource^ source = gcnew HwndSource(
0, // class style
WS_VISIBLE | WS_CHILD, // style
0, // exstyle
x, y, width, height,
"hi", // NAME
IntPtr(parent) // parent window
);

UIElement^ page = gcnew ManagedContent::WPFContent();
source->RootVisual = page;
return (HWND)source->Handle.ToPointer();
}
}

and finally

1
2
//managed content
ManagedCode::GetHwnd(hWnd, 0, 0, 200, 200);

Due to the different technologies behind WPF and GDI, more work needed to be done for the unavoidable alpha blending problem, but, you can use wpf popup to achieve your goal for short.

使用 OpenCL 实现图片高斯模糊

高斯模糊( https://zh.wikipedia.org/wiki/%E9%AB%98%E6%96%AF%E6%A8%A1%E7%B3%8A )是一种常见的图像处理算法,使用高斯分布与图像做卷积,得到模糊的效果。其二维定义:

σ是正态分布的标准偏差。在应用的时候,假设σ为2.5。对于模糊半径为1,则高斯矩阵为3*3的一个矩阵,以[1,1]为中心,带入公式计算高斯矩阵的值,得到:

0.0216996633 0.0235069655 0.0216996633
0.0235069655 0.0254647918 0.0235069655
0.0216996633 0.0235069655 0.0216996633

他们的和为 0.206291318,我们需要他们的和为1,因此与总和相除得到:

0.105189413 0.113950342 0.105189413
0.113950342 0.123440929 0.113950342
0.105189413 0.113950342 0.105189413

根据这个矩阵,对图像的每个像素点进行计算,计算的九个点的各rgb分量之和就是最终像素的rgb分量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
//计算高斯矩阵
private void ComputeWeightMatrix()
{
var center = Radius;
var conBase = 2 * Math.Pow(Variance, 2);
var conRoot = 1 / (Math.PI * conBase);

float sum = 0f;
for (int x = -Radius; x <= Radius; x++)
{
for (int y = Radius; y >= -Radius; y--)
{
var weight = conRoot * Math.Pow(Math.E, -(x * x + y * y) / conBase);
_matrix[GridPosToArrayIndex(x, y, center, Radius)] = (float)weight;
sum += (float)weight;
}
}
for (int i = 0; i < _matrix.Length; i++)
{
_matrix[i] /= sum;
}
}

//Compute
public void Compute(string imageFile)
{
using (var bitmap = new Bitmap(imageFile))
{
var datas = bitmap.LockBits(new Rectangle(new Point(), new Size(bitmap.Width, bitmap.Height)),ImageLockMode.ReadOnly,bitmap.PixelFormat);
var dataSize = datas.Stride * datas.Height;
var argbs = new byte[dataSize];
var dsts = new byte[dataSize];
int matrixWidth = Radius * 2 + 1;
Marshal.Copy(datas.Scan0, argbs, 0, dataSize);

Stopwatch sw=Stopwatch.StartNew();
for (int y = 0; y < bitmap.Height; y++)
{
for (int x = 0; x < bitmap.Width; x++)
{
float sumA = 0;
float sumR = 0;
float sumG = 0;
float sumB=0;
for (int i = 0; i < _matrix.Length; i++)
{
var pos = transform_pos(x, y, matrixWidth, bitmap.Width, bitmap.Height, Radius, i);
var position = pos.Y * datas.Stride + pos.X*4;
sumR += argbs[position] * _matrix[i];
sumG += argbs[position + 1] * _matrix[i];
sumB += argbs[position + 2] * _matrix[i];
sumA += argbs[position + 3] * _matrix[i];
}
var dstPos = y * datas.Stride + x * 4;
dsts[dstPos] = (byte)sumR;
dsts[dstPos+1] = (byte)sumG;
dsts[dstPos+2] = (byte)sumB;
dsts[dstPos+3] = (byte)sumA;
}
}
bitmap.UnlockBits(datas);

var elapse = sw.Elapsed;
Console.WriteLine($"Costing: {elapse}");
Debug.WriteLine($"Costing: {elapse}");

var handle = GCHandle.Alloc(dsts, GCHandleType.Pinned);
using (var dstBmp = new Bitmap(datas.Width, datas.Height, datas.Stride, bitmap.PixelFormat,
handle.AddrOfPinnedObject()))
{
dstBmp.Save("processed_normal.bmp");
}

handle.Free();
}
}

当然,这样能完成工作,但是耗时太长,对于3000*1920尺寸的图片处理需要2分51秒(Intel Core i7-4770),这显然是不可接受的。
对于这种分别计算每个像素,且各像素间互不干扰的问题,使用OpenCL可以大幅降低时间消耗。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
/*
OpenCL 高斯模糊代码
Copyright Gandalfliang
*/
inline int2 transform_pos(int centerX,int centerY,int matrixWidth,int radius,int index)
{
int x=index%matrixWidth;
int offsetX=x-(radius+1);
int y=index/matrixWidth;
int offsetY=radius-y;
return (int2)(centerX+offsetX,centerY-offsetY);
};

const sampler_t sampler_img=CLK_NORMALIZED_COORDS_FALSE|CLK_ADDRESS_CLAMP_TO_EDGE;

//opencl kernel 代码
kernel void gaussian_blur(
read_only image2d_t src,
global write_only char* dst,
global read_only float* matrix,
read_only int radius,
read_only int width)
{
int x=get_global_id(0);
int y=get_global_id(1);

float sumR,sumG,sumB,sumA;
int matrixWidth=radius*2+1;
int matrix_size=pow(matrixWidth,2);
for(int i=0;i<matrix_size;i++)
{
int2 pix=transform_pos(x,y,matrixWidth,radius,i);
uint4 rgba = read_imageui(src,sampler_img,pix);
sumR+=rgba.x*matrix[i];
sumG+=rgba.y*matrix[i];
sumB+=rgba.z*matrix[i];
sumA+=rgba.w*matrix[i];
}

int loc=y*width*4+x*4;
dst[loc]=sumR;
dst[loc+1]=sumG;
dst[loc+2]=sumB;
dst[loc+3]=sumA;
}

Host代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
public void Compute_cl(string imageFile)
{
//选取设备
var platform = ComputePlatform.Platforms.FirstOrDefault();
var device = platform.Devices.FirstOrDefault();
//设置相关上下文
var properties = new ComputeContextPropertyList(platform);
var context = new ComputeContext(new[] {device}, properties, null, IntPtr.Zero);
//命令队列,用于控制执行的代码
ComputeCommandQueue commands = new ComputeCommandQueue(context, context.Devices[0],
ComputeCommandQueueFlags.None);
//读取opencl代码
var code = File.ReadAllText(@"gaussianblur.cl");
//编译
var program = new ComputeProgram(context, code);
try
{
program.Build(new[] {device}, null, null, IntPtr.Zero);
}
catch (Exception ex)
{
throw;
}

var images = CreateImageFromBitmap(imageFile, context,
ComputeMemoryFlags.ReadWrite | ComputeMemoryFlags.CopyHostPointer);

//创建核心代码,就是cl代码中以kernel标识的函数
var kernel = program.CreateKernel("gaussian_blur");
//矩阵规模
//储存计算结果的数组

//创建的核心代码函数以这种方式来传参
var resultBuffer=new ComputeBuffer<char>(context,ComputeMemoryFlags.WriteOnly, dstBytes.Length);
kernel.SetMemoryArgument(0, images);
kernel.SetMemoryArgument(1, resultBuffer);
kernel.SetMemoryArgument(2, new ComputeBuffer<float>(context,ComputeMemoryFlags.ReadOnly|ComputeMemoryFlags.CopyHostPointer,_matrix));
kernel.SetValueArgument(3, Radius);
kernel.SetValueArgument(4, (int)images.Width);
Console.WriteLine($"运行平台: {platform.Name}\n运行设备: {device.Name}\n");
Stopwatch sw = Stopwatch.StartNew();
var climg = images;

//执行代码
commands.Execute(kernel, null, new long[] {climg.Width, climg.Height}, null, null);

//read data
char[] resultArray = new char[dstBytes.Length];
var arrHandle = GCHandle.Alloc(resultArray, GCHandleType.Pinned);
commands.Read(resultBuffer, true, 0, dstBytes.Length, arrHandle.AddrOfPinnedObject(), null);
//commands.ReadFromImage(images.Item2, processeddata.Scan0, true, null);

var resultHandle = GCHandle.Alloc(resultArray, GCHandleType.Pinned);
var bmp=new Bitmap(climg.Width,climg.Height, climg.Width*4, PixelFormat.Format32bppArgb, resultHandle.AddrOfPinnedObject());
var elapsed = sw.Elapsed;
Console.WriteLine($"耗时: {elapsed.TotalMilliseconds} ms\n");
kernel.Dispose();

bmp.Save("processed_cl.bmp");
}

相同尺寸的图片处理,使用 Intel Core i7-4770 自带的核显 HD4600 处理,耗时只需要164毫秒。

以下是相关测试结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
运行平台: Intel(R) OpenCL
运行设备: Intel(R) HD Graphics 4600
处理图片尺寸:790*501
OpenCL处理耗时: 13.6597 ms

处理图片尺寸:790*501
常规方法耗时: 11482.9402 ms

运行平台: Intel(R) OpenCL
运行设备: Intel(R) HD Graphics 4600
处理图片尺寸:1339*693
OpenCL处理耗时: 33.0095 ms

处理图片尺寸:1339*693
常规方法耗时: 26908.9926 ms

运行平台: Intel(R) OpenCL
运行设备: Intel(R) HD Graphics 4600
处理图片尺寸:1920*1080
OpenCL处理耗时: 51.3885 ms

处理图片尺寸:1920*1080
常规方法耗时: 60147.3815 ms

当然,常规方法都只使用了单线程,还未发挥多核CPU的威力,然而,可以预见的是,即使是使用多线程,提升也是有限的。

原图:

高斯模糊:

代码: https://github.com/gandalfliang/cloo_netstandard/tree/temp

Update: 在nVidia的环境下会导致处理后的图片出现花屏现象,估计是cl代码的问题,又或者是nVidia的驱动有问题?下次再更新