- c - 在位数组中找到第一个零
- linux - Unix 显示有关匹配两种模式之一的文件的信息
- 正则表达式替换多个文件
- linux - 隐藏来自 xtrace 的命令
我目前正在做微基准测试,以便更好地了解 clr 性能和版本问题。所讨论的微基准是将每个 64 字节的两个字节数组异或在一起。
在尝试使用 unsafe
等击败 .net 框架实现之前,我总是使用安全的 .net 进行引用实现。
我的引用实现是:
for (int p = 0; p < 64; p++)
a[p] ^= b[p];
其中 a
和 b
是 byte[] a = new byte[64]
并填充了来自 .NET rng 的数据。
此代码在 x64 上的运行速度是在 x86 上的两倍。首先我认为这没问题,因为 jit 会在 x86 上生成类似 *long^=*long
和 *int^=*int
的东西。
但是我优化的不安全版本:
fixed (byte* pA = a)
fixed (byte* pB = b)
{
long* ppA = (long*)pA;
long* ppB = (long*)pB;
for (int p = 0; p < 8; p++)
{
*ppA ^= *ppB;
ppA++;
ppB++;
}
}
运行速度比 x64 引用实现快约 4 倍。所以我对*long^=*long
和*int^=*int
编译器优化的想法是不对的。
引用实现中这种巨大的性能差异从何而来?现在我发布了 ASM 代码:为什么 C# 编译器不能以这种方式优化 x86 版本?
x86 和 x64 引用实现的 IL 代码(它们是相同的):
IL_0059: ldloc.3
IL_005a: ldloc.s p
IL_005c: ldelema [mscorlib]System.Byte
IL_0061: dup
IL_0062: ldobj [mscorlib]System.Byte
IL_0067: ldloc.s b
IL_0069: ldloc.s p
IL_006b: ldelem.u1
IL_006c: xor
IL_006d: conv.u1
IL_006e: stobj [mscorlib]System.Byte
IL_0073: ldloc.s p
IL_0075: ldc.i4.1
IL_0076: add
IL_0077: stloc.s p
IL_0079: ldloc.s p
IL_007b: ldc.i4.s 64
IL_007d: blt.s IL_0059
我认为 ldloc.3
是 a
。
为 x86 生成的 ASM 代码:
for (int p = 0; p < 64; p++)
010900DF xor edx,edx
010900E1 mov edi,dword ptr [ebx+4]
a[p] ^= b[p];
010900E4 cmp edx,edi
010900E6 jae 0109010C
010900E8 lea esi,[ebx+edx+8]
010900EC mov eax,dword ptr [ebp-14h]
010900EF cmp edx,dword ptr [eax+4]
010900F2 jae 0109010C
010900F4 movzx eax,byte ptr [eax+edx+8]
010900F9 xor byte ptr [esi],al
for (int p = 0; p < 64; p++)
010900FB inc edx
010900FC cmp edx,40h
010900FF jl 010900E4
生成的 x64 的 ASM 代码:
a[p] ^= b[p];
00007FFF4A8B01C6 mov eax,3Eh
00007FFF4A8B01CB cmp rax,rcx
00007FFF4A8B01CE jae 00007FFF4A8B0245
00007FFF4A8B01D0 mov rax,qword ptr [rbx+8]
00007FFF4A8B01D4 mov r9d,3Eh
00007FFF4A8B01DA cmp r9,rax
00007FFF4A8B01DD jae 00007FFF4A8B0245
00007FFF4A8B01DF mov r9d,3Fh
00007FFF4A8B01E5 cmp r9,rcx
00007FFF4A8B01E8 jae 00007FFF4A8B0245
00007FFF4A8B01EA mov ecx,3Fh
00007FFF4A8B01EF cmp rcx,rax
00007FFF4A8B01F2 jae 00007FFF4A8B0245
00007FFF4A8B01F4 nop word ptr [rax+rax]
00007FFF4A8B0200 movzx ecx,byte ptr [rdi+rdx+10h]
00007FFF4A8B0205 movzx eax,byte ptr [rbx+rdx+10h]
00007FFF4A8B020A xor ecx,eax
00007FFF4A8B020C mov byte ptr [rdi+rdx+10h],cl
00007FFF4A8B0210 movzx ecx,byte ptr [rdi+rdx+11h]
00007FFF4A8B0215 movzx eax,byte ptr [rbx+rdx+11h]
00007FFF4A8B021A xor ecx,eax
00007FFF4A8B021C mov byte ptr [rdi+rdx+11h],cl
00007FFF4A8B0220 add rdx,2
for (int p = 0; p < 64; p++)
00007FFF4A8B0224 cmp rdx,40h
00007FFF4A8B0228 jl 00007FFF4A8B0200
最佳答案
您犯了一个典型的错误,试图对未优化的代码进行性能分析。这是一个完整的最小可编译示例:
using System;
namespace SO30558357
{
class Program
{
static void XorArray(byte[] a, byte[] b)
{
for (int p = 0; p< 64; p++)
a[p] ^= b[p];
}
static void Main(string[] args)
{
byte[] a = new byte[64];
byte[] b = new byte[64];
Random r = new Random();
r.NextBytes(a);
r.NextBytes(b);
XorArray(a, b);
Console.ReadLine(); // when the program stops here
// use Debug -> Attach to process
}
}
}
我使用 Visual Studio 2013 Update 3 编译了它,C# 控制台应用程序的默认“Release Build”设置(体系结构除外),并使用 CLR v4.0.30319 运行它。哦,我想我已经安装了 Roslyn,但这不应该取代 JIT,只能取代在两种架构上相同的 MSIL。
XorArray
的实际 x86 程序集:
006F00D8 push ebp
006F00D9 mov ebp,esp
006F00DB push edi
006F00DC push esi
006F00DD push ebx
006F00DE push eax
006F00DF mov dword ptr [ebp-10h],edx
006F00E2 xor edi,edi
006F00E4 mov ebx,dword ptr [ecx+4]
006F00E7 cmp edi,ebx
006F00E9 jae 006F010F
006F00EB lea esi,[ecx+edi+8]
006F00EF movzx eax,byte ptr [esi]
006F00F2 mov edx,dword ptr [ebp-10h]
006F00F5 cmp edi,dword ptr [edx+4]
006F00F8 jae 006F010F
006F00FA movzx edx,byte ptr [edx+edi+8]
006F00FF xor eax,edx
006F0101 mov byte ptr [esi],al
006F0103 inc edi
006F0104 cmp edi,40h
006F0107 jl 006F00E7
006F0109 pop ecx
006F010A pop ebx
006F010B pop esi
006F010C pop edi
006F010D pop ebp
006F010E ret
对于 x64:
00007FFD4A3000FB mov rax,qword ptr [rsi+8]
00007FFD4A3000FF mov rax,qword ptr [rbp+8]
00007FFD4A300103 nop word ptr [rax+rax]
00007FFD4A300110 movzx ecx,byte ptr [rsi+rdx+10h]
00007FFD4A300115 movzx eax,byte ptr [rdx+rbp+10h]
00007FFD4A30011A xor ecx,eax
00007FFD4A30011C mov byte ptr [rsi+rdx+10h],cl
00007FFD4A300120 movzx ecx,byte ptr [rsi+rdx+11h]
00007FFD4A300125 movzx eax,byte ptr [rdx+rbp+11h]
00007FFD4A30012A xor ecx,eax
00007FFD4A30012C mov byte ptr [rsi+rdx+11h],cl
00007FFD4A300130 movzx ecx,byte ptr [rsi+rdx+12h]
00007FFD4A300135 movzx eax,byte ptr [rdx+rbp+12h]
00007FFD4A30013A xor ecx,eax
00007FFD4A30013C mov byte ptr [rsi+rdx+12h],cl
00007FFD4A300140 movzx ecx,byte ptr [rsi+rdx+13h]
00007FFD4A300145 movzx eax,byte ptr [rdx+rbp+13h]
00007FFD4A30014A xor ecx,eax
00007FFD4A30014C mov byte ptr [rsi+rdx+13h],cl
00007FFD4A300150 add rdx,4
00007FFD4A300154 cmp rdx,40h
00007FFD4A300158 jl 00007FFD4A300110
底线:x64 优化器工作得更好。虽然它仍在使用 byte
大小的传输,但它将循环展开了 4 倍,并内联了函数调用。
由于在 x86 版本中,循环控制逻辑对应于大约一半的代码,展开可以预期产生几乎两倍的性能。
内联允许编译器执行上下文相关的优化,了解数组的大小并消除运行时边界检查。
如果我们手动内联,x86 编译器现在会产生:
00A000B1 xor edi,edi
00A000B3 mov eax,dword ptr [ebp-10h]
00A000B6 mov ebx,dword ptr [eax+4]
a[p] ^= b[p];
00A000B9 mov eax,dword ptr [ebp-10h]
00A000BC cmp edi,ebx
00A000BE jae 00A000F5
00A000C0 lea esi,[eax+edi+8]
00A000C4 movzx eax,byte ptr [esi]
00A000C7 mov edx,dword ptr [ebp-14h]
00A000CA cmp edi,dword ptr [edx+4]
00A000CD jae 00A000F5
00A000CF movzx edx,byte ptr [edx+edi+8]
00A000D4 xor eax,edx
00A000D6 mov byte ptr [esi],al
for (int p = 0; p< 64; p++)
00A000D8 inc edi
00A000D9 cmp edi,40h
00A000DC jl 00A000B9
没有太大帮助,循环仍然没有展开,运行时边界检查仍然存在。
值得注意的是,x86 编译器找到了一个寄存器 (EBX
) 来缓存一个数组的长度,但用完了寄存器,并被迫在每次迭代时从内存中访问另一个数组长度。这应该是一种“廉价”的 L1 缓存访问,但这仍然比寄存器访问慢,而且比根本没有边界检查要慢得多。
关于c# - x64 和 x86 之间字节数组访问的巨大性能差异,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30558357/
美好的一天!我试图添加两个字节变量并注意到奇怪的结果。 byte valueA = 255; byte valueB = 1; byte valueC = (byte)(valueA + valueB
嗨,我是 swift 的新手,我正在尝试解码以 [Byte] 形式发回给我的字节数组?当我尝试使用 if let string = String(bytes: d, encoding: .utf8)
我正在使用 ipv4 和 ipv6 存储在 postgres 数据库中。 因为 ipv4 需要 32 位(4 字节)而 ipv6 需要 128(16 字节)位。那么为什么在 postgres 中 CI
我很好奇为什么 Go 不提供 []byte(*string) 方法。从性能的角度来看,[]byte(string) 不会复制输入参数并增加更多成本(尽管这看起来很奇怪,因为字符串是不可变的,为什么要复
我正在尝试为UDP实现Stop-and-Wait ARQ。根据停止等待约定,我在 0 和 1 之间切换 ACK。 正确的 ACK 定义为正确的序列号(0 或 1)AND消息长度。 以下片段是我的代码的
我在下面写了一些代码,目前我正在测试,所以代码中没有数据库查询。 下面的代码显示 if(filesize($filename) != 0) 总是转到 else,即使文件不是 0 字节而是 16 字节那
我使用 Apache poi 3.8 来读取 xls 文件,但出现异常: java.io.IOException: Unable to read entire header; 0 by
字典大小为 72 字节(根据 getsizeof(dict) 在字典上调用 .clear() 之后发生了什么,当新实例化的字典返回 240 字节时? 我知道一个简单的 dict 的起始大小为“8”,并
我目前正在努力创建一个函数,它接受两个 4 字节无符号整数,并返回一个 8 字节无符号长整数。我试图将我的工作基于 this research 描述的方法,但我的所有尝试都没有成功。我正在处理的具体输
看看这个简单的程序: #include using namespace std; int main() { unsigned int i=0x3f800000; float* p=(float*)(
我创建了自己的函数,将一个字符串转换为其等效的 BCD 格式的 bytes[]。然后我将此字节发送到 DataOutputStram (使用需要 byte[] 数组的写入方法)。问题出在数字字符串“8
此分配器将在具有静态内存的嵌入式系统中使用(即,没有可用的系统堆,因此“堆”将只是“char heap[4096]”) 周围似乎有很多“小型内存分配器”,但我正在寻找能够处理非常小的分配的一个。我说的
我将数据库脚本从 64 位系统传输到 32 位系统。当我执行脚本时,出现以下错误, Warning! The maximum key length is 900 bytes. The index 'U
想知道 128 字节 ext2 和 256 字节 ext3 文件系统之间的 inode 数据结构差异。 我一直在为 ext2、128 字节 inode 使用此引用:http://www.nongnu.
我试图理解使用 MD5 哈希作为 Cassandra key 在“内存/存储消耗”方面的含义: 我的内容(在 Java 中)的 MD5 哈希 = byte[] 长 16 个字节。 (16 字节来自维基
检查其他人是否也遇到类似问题。 shell脚本中的代码: ## Convert file into Unix format first. ## THIS is IMPORTANT. ###
我们有一个测量数据处理应用程序,目前所有数据都保存为 C++ float,这意味着在我们的 x86/Windows 平台上为 32 位/4 字节。 (32 位 Windows 应用程序)。 由于精度成
我读到在 Java 中 long 类型可以提升为 float 和 double ( http://www.javatpoint.com/method-overloading-in-java )。我想问
我有一个包含 n 个十进制元素的列表,其中每个元素都是两个字节长。 可以说: x = [9000 , 5000 , 2000 , 400] 这个想法是将每个元素拆分为 MSB 和 LSB 并将其存储在
我使用以下代码进行 AES-128 加密来编码一个 16 字节的 block ,但编码值的长度给出了 2 个 32 字节的 block 。我错过了什么吗? plainEnc = AES.enc
我是一名优秀的程序员,十分优秀!