我自己的问题中描述的问题似乎可以通过两种简单的方式解决(查看有问题的图片):
1)你有橙色和绿色块,你需要交换它们 - 将橙色块放在缓冲区中(因为它更小),移动绿色块,然后他们从缓冲区中获取橙色块。
权衡:简单、明显、快速的小块
问题:如果橙色和绿色块的大小相同 - 您将需要最大的缓冲区,并且可能会占用大量内存
2)有问题描述的使用方式。您将能够使用不大于两个块大小(橙色和绿色)的 GCD 的缓冲区。因此,最好使用两个缓冲区:一个具有 CPU 寄存器大小(我的 amd64 为 8 个字节)和一个字节大小的缓冲区。因此,首先您使用(GCD div 8)(对于 64 位系统)步骤使用寄存器大小的缓冲区移动(或移位),然后完成字节大小的缓冲区。
权衡:不需要大缓冲
问题:如果两个块大小的 GCD 小于寄存器大小 - 我们会非常慢(因为在这种情况下只有字节大小的移位操作可用)。一点都不明显。
我做了两个函数来测试这两个解决方案
// License: LGPL
//──────────────────────────────────────────────────────────────────────────────
procedure MemoryMove(const Pos1, Pos2: Pointer; const Len: PtrUInt);
var
// Memory addresses
MemPos1: PtrUInt; // bottom block address
MemPos2: PtrUInt; // top block address
CurPos: PtrUInt; // current position
CurPos1: PtrUInt; // current position 1
CurPos2: PtrUInt; // current position 2
CurPosLimit: PtrUInt;
// Memory sizes
MemSize1: PtrUInt; // size of bottom memory block
MemSize2: PtrUInt; // size of top memory block
// Counters
BufSizeReq: PtrUInt; // buffer size required
// Counters for buffers
BufCount: PtrUInt;
// Mini buffers
BufReg: PtrUInt; // buffer match pointer (registers) size
BufByte: Int8U; // byte sized buffer
// Temporary variables
TempReg: PtrUInt;
TempByte: Int8U;
// Variables for iterations
Count: IntU;
begin
// This code is not obvious, see manual or other documentation
// http://stackoverflow.com/questions/18539341/move-bytes-in-memory-without-replacing-existing-bytes-and-without-big-buffer
// Check parameters
// for being out of memory bounds
if (PtrUInt(Pos1) > High(PtrUInt) - Len) OR (PtrUInt(Pos2) > High(PtrUInt) - Len) then
begin
raise Exception.Create(rsMemOutOfBounds);
end;
// Does it make sense to do anything?
if (Pos1 = Pos2) OR (Len = 0) then
begin
Exit;
end;
// Move operation interpreted as cycle shifting from low to high address
// Convert parameters of Move operation to cycle shifting
if Pos1 < Pos2 then
begin
MemPos1 := PtrUInt(Pos1);
MemPos2 := PtrUInt(Pos2);
MemSize1 := Len;
MemSize2 := PtrUInt(Pos2) - PtrUInt(Pos1);
end
else
begin
MemPos1 := PtrUInt(Pos2);
MemPos2 := PtrUInt(Pos2) + Len;
MemSize1 := PtrUInt(Pos1) - PtrUInt(Pos2);
MemSize2 := Len;
end;
// Buffer can't be bigger than GCD of two block sizes
BufSizeReq := GCD(MemSize1, MemSize2);
// Prepare some variables
CurPos1 := MemPos1;
CurPos2 := MemPos2;
BufCount := 0;
// Shift with different buffer sizes
// Start with biggest, end with smallest
// Using pointer-size buffer
// Calculate amount of mini buffers needed to fit required buffer size
if SizeOf(Pointer) >= SizeOf(BufReg) then
begin
BufCount := BufSizeReq div SizeOf(BufReg);
BufSizeReq := BufSizeReq - BufCount * SizeOf(BufReg);
end;
// Shift data with current buffer size and count
Count := 1;
while Count <= BufCount do
begin
// First step
BufReg := PtrUInt(Pointer(CurPos2)^);
PtrUInt(Pointer(CurPos2)^) := PtrUInt(Pointer(CurPos1)^);
// Next steps done in loop
CurPos := CurPos2;
CurPosLimit := CurPos2 - MemSize2 + MemSize1;
repeat
// Calculate where current element from buffer should be placed
if (CurPos < CurPosLimit) then
begin
CurPos := CurPos + MemSize2;
end
else
begin
CurPos := CurPos - MemSize1;
end;
// Exchange value from buffer with current memory position
TempReg := BufReg;
BufReg := PtrUInt(Pointer(CurPos)^);
PtrUInt(Pointer(CurPos)^) := TempReg;
until CurPos = CurPos1; // very bad condition - one mistake and loop will be infinite
CurPos1 := CurPos1 + SizeOf(BufReg);
CurPos2 := CurPos2 + SizeOf(BufReg);
Count := Count + 1;
end;
// NOTE: below code is copy-paste+edit of code above
// It's very bad for readability, but good for speed
// Using one-byte-size buffer
// This **IS** necessarily
// Calculate amount of mini buffers needed to fit required buffer size
BufCount := BufSizeReq;
// Shift data with current buffer size and count
Count := 1;
while Count <= BufCount do
begin
// First step
BufByte := Int8U(Pointer(CurPos2)^);
Int8U(Pointer(CurPos2)^) := Int8U(Pointer(CurPos1)^);
// Next steps done in loop
CurPos := CurPos2;
CurPosLimit := CurPos2 - MemSize2 + MemSize1;
repeat
// Calculate where current element from buffer should be placed
if (CurPos < CurPosLimit) then
begin
CurPos := CurPos + MemSize2;
end
else
begin
CurPos := CurPos - MemSize1;
end;
// Exchange value from buffer with current memory position
TempByte := BufByte;
BufByte := Int8U(Pointer(CurPos)^);
Int8U(Pointer(CurPos)^) := TempByte;
until CurPos = CurPos1; // very bad condition - one mistake and loop will be infinite
CurPos1 := CurPos1 + 1;
CurPos2 := CurPos2 + 1;
Count := Count + 1;
end;
end;
//──────────────────────────────────────────────────────────────────────────────
procedure MemoryMoveBuf(const Pos1, Pos2: Pointer; const Len: PtrUInt);
var
Buf: Int8UAD; // buffer
MemPos11, MemPos12, MemPos21, MemPos22: PtrUInt;
MemLen1, MemLen2: PtrUInt;
begin
// Check parameters
// for being out of memory bounds
if (PtrUInt(Pos1) > High(PtrUInt) - Len) OR (PtrUInt(Pos2) > High(PtrUInt) - Len) then
begin
raise Exception.Create(rsMemOutOfBounds);
end;
// Does it make sense to do anything?
if (Pos1 = Pos2) OR (Len = 0) then
begin
Exit;
end;
// There are two memory blocks
// First is given in parameters
MemPos11 := PtrUInt(Pos1);
MemPos12 := PtrUInt(Pos2);
MemLen1 := Len;
// The second one must be calculated
if Pos2 > Pos1 then
begin
MemPos21 := PtrUInt(Pos1) + Len;
MemPos22 := PtrUInt(Pos1);
MemLen2 := PtrUInt(Pos2) - PtrUInt(Pos1);
end
else
begin
MemPos21 := PtrUInt(Pos2);
MemPos22 := PtrUInt(Pos2) + Len;
MemLen2 := PtrUInt(Pos1) - PtrUInt(Pos2);
end;
// Moving
try
// Use smaller buffer
if MemLen1 <= MemLen2 then
begin
SetLength(Buf, MemLen1);
system.Move(Pointer(MemPos11)^, Pointer(Buf)^, MemLen1);
system.Move(Pointer(MemPos21)^, Pointer(MemPos22)^, MemLen2);
system.Move(Pointer(Buf)^, Pointer(MemPos12)^, MemLen1);
end
else
begin
SetLength(Buf, MemLen2);
system.Move(Pointer(MemPos21)^, Pointer(Buf)^, MemLen2);
system.Move(Pointer(MemPos11)^, Pointer(MemPos12)^, MemLen1);
system.Move(Pointer(Buf)^, Pointer(MemPos22)^, MemLen2);
end;
finally
SetLength(Buf, 0);
end;
end;
//──────────────────────────────────────────────────────────────────────────────
对于测试,我创建了缓冲区,将下半部分移动到上半部分(交换缓冲区数据的一半) - 这是解决方案 1 的最坏情况。
我用相同的缓冲区大小重复了每一次移动测试以获得平均结果。
所以我从 512 字节缓冲区开始,将 256 字节从位置 0 移动到位置 256,重复此移动 1048576 次,然后使缓冲区大 2 倍,并减少重复 2 次,...,以 536870912 字节缓冲区结束,只有 1 次重复(总共 21 次大迭代)
这给出了良好的 GCD(GCD > 寄存器大小)。为了模拟解决方案 2 的最坏情况,当 (GCD = 1) 时,我只是将移动长度减少 1,因此在第一次迭代中,它从 pos 移动了 255 个字节。0到位置。256.
我很失望——我最喜欢的解决方案 2 的自写函数很慢,即使是 (GCD > 8),当 (GCD = 1) 时——它非常慢。
图 1
此图显示了结果。OX - 缓冲区大小,OY - 时间(更少 = 更好)。实心黑色圆圈和粗线 - 解决方案 1。底部白色圆圈和细线是具有良好 GCD(GCD > 8,最佳情况)的解决方案 2,顶线 - 坏 GCD(GCD = 1,最坏情况)。灰色填充 - 解决方案 2 的中间值。
说明:解决方案1使用专业程序员在汇编程序中编写的“移动”功能,很难通过性能击败它。
几周后,我发现编译器没有给出最佳的汇编代码。通过一些额外的优化(-O3 -Or),结果会变得更好
图 2
结论:我已经测试了这两个函数来给出相同的结果,但是当我做那个图形时没有,所以它可能都是错误的。
当您要移动的内存块之一很小时,解决方案 1 似乎非常好。但是它有一些瓶颈并且不是很线性(因此很难预测执行时间)。
当您受限于分配更多内存时,解决方案 2 似乎适用于移动大量内存。这是一个非常线性的并且可以通过执行时间来预测。但是可能非常慢。