【发布时间】:2022-01-26 04:22:51
【问题描述】:
当我使用 (Loops like 'for') 将数据从输入 (ReadOnlySpan) 复制到输出 (Span) 时遇到性能问题
有Span.CopyTo,非常完美而且速度非常快 但是现在如果不转换像素就没用了。
下面是代码,我觉得有一些简短的方法可以代替当前的过程:
public unsafe void UpdateFromOutput(CanvasDevice device, ReadOnlySpan<byte> data, uint width, uint height, uint pitch)
{
using (var renderTargetMap = new BitmapMap(device, RenderTarget))
{
var inputPitch = (int)pitch;
var mapPitch = (int)renderTargetMap.PitchBytes;
var mapData = new Span<byte>(new IntPtr(renderTargetMap.Data).ToPointer(), (int)RenderTarget.Size.Height * mapPitch);
switch (CurrentPixelFormat)
{
case PixelFormats.RGB0555:
FramebufferConverter.ConvertFrameBufferRGB0555ToXRGB8888(width, height, data, inputPitch, mapData, mapPitch);
break;
case PixelFormats.RGB565:
FramebufferConverter.ConvertFrameBufferRGB565ToXRGB8888(width, height, data, inputPitch, mapData, mapPitch);
break;
}
}
}
然后像 ConvertFrameBufferRGB0555ToXRGB8888
之类的内部函数我会像下面这样遍历宽度和高度:
var castInput = MemoryMarshal.Cast<byte, ushort>(input);
var castInputPitch = inputPitch / sizeof(ushort);
var castOutput = MemoryMarshal.Cast<byte, uint>(output);
var castOutputPitch = outputPitch / sizeof(uint);
castOutput.Fill(0);
for (var i = 0; i < height;i++)
{
var inputLine = castInput.Slice(i * castInputPitch, castInputPitch);
var outputLine = castOutput.Slice(i * castOutputPitch, castOutputPitch);
for (var j = 0; j < width;j++)
{
outputLine[j] = ConverToRGB888(inputLine[j]);
}
}
上面的代码可以运行,但在某些情况下速度很慢。
请注意:我正在修改一个项目,所以上面的代码是由原始开发人员编写的,我需要帮助,因为我不明白这个过程是如何工作的,仍然很困惑..特别是在 Slice 部分。
尝试仅测试将输入直接复制到输出data.CopyTo(mapData);,我得到了这个(如预期的那样):
希望有一些使用 Marshal 和 Span 函数的解决方案
非常感谢。
关于 (ConverToRGB888) 的更新
至于ConverToRGB888,原代码包含RGB565LookupTable:
private const uint LookupTableSize = ushort.MaxValue + 1;
private static uint[] RGB565LookupTable = new uint[LookupTableSize];
public static void SetRGB0565LookupTable()
{
uint r565, g565, b565;
double red = 255.0;
double green = 255.0;
double blue = 255.0;
for (uint i = 0; i < LookupTableSize; i++)
{
//RGB565
r565 = (i >> 11) & 0x1F;
g565 = (i >> 5) & 0x3F;
b565 = (i & 0x1F);
r565 = (uint)Math.Round(r565 * red / 31.0);
g565 = (uint)Math.Round(g565 * green / 63.0);
b565 = (uint)Math.Round(b565 * blue / 31.0);
RGB565LookupTable[i] = (0xFF000000 | r565 << 16 | g565 << 8 | b565);
}
}
private static uint ConverToRGB888(ushort x)
{
return RGB565LookupTable[x];
}
SetRGB0565LookupTable() 只会被调用一次来填充值。
结论:
-
Fill(0)不重要,导致延迟 - 不安全的版本(接受的答案)对我来说很清楚,而且速度有点快
- 部分避免
For甚至更快,例如 Here [已测试] - 预查找表非常有用,可以加快转换速度
-
Span.CopyTo、Buffer.MemoryCopy等内存助手可用源 Here - 在 UnsafeMemory 的帮助下,在某些情况下使用
Parallel.For会更快 - 如果您使用 Win2D 支持的(像素类型)输入,则可以避免以下循环:
byte[] dataBytes = new byte[data.Length];
fixed (byte* inputPointer = &data[0])
Marshal.Copy((IntPtr)inputPointer, dataBytes, 0, data.Length);
RenderTarget = CanvasBitmap.CreateFromBytes(renderPanel, dataBytes, (int)width, (int)height, DirectXPixelFormat.R8G8UIntNormalized, 92, CanvasAlphaMode.Ignore);
但从最后一点不确定,因为我无法在 565,555 上进行测试。
感谢DekuDesu他提供的解释和简化版帮助我做更多的测试。
【问题讨论】:
-
请尝试缩进您的代码。它会更容易阅读。
-
可以看看
ConverToRGB888的定义和实现吗? -
@DekuDesu 我有 RGB565/RGB555 输入,我需要以任何可能的方式将其作为 RGB888 发送到输出,但循环次数更少。
-
@DekuDesu 我更新了代码并添加了 ConverToRGB888,我正在寻找使用 Marshal 和 Span.CopyTo 的解决方案,如果可能的话,最多只有一个“循环”。跨度>
-
看起来您只是将每个像素从 RGB565/555 转换为 RGB88,并且您有两个
for循环。这是 O(n),您的查找是 O(1)。除非您跳过像素,否则那里没有任何改进。下一个可能的选项是避免全部切片,这充其量只会给您带来边际改进。您可以尝试手动处理字节而不是 Marshall.cast 位,这样做将使用 unsafe 字节操作。这可能最多可以为您节省一些内存副本。
标签: c# memory-management uwp