创建一个常量但本地的数组

2024-01-14

有时我需要针对单个方法的硬编码查找表。

我也可以创建这样一个数组

  • 在方法本身本地
  • 类内静态

第一种情况的示例:

public int Convert(int i)
{
    int[] lookup = new[] {1, 2, 4, 8, 16, 32, 666, /*...*/ };
    return lookup[i];
}

据我了解,每次执行此方法时,.net 引擎都会创建一个新的查找数组。这是正确的吗,或者JITer 是否足够智能,可以在调用之间缓存和重用数组?

我认为答案是否定的,所以如果我想确保数组在调用之间被缓存,一种方法是使它static:

第二种情况的例子:

private static readonly int[] lookup = new[] { 1, 2, 4, 8, 16, 32, 666, /*...*/ };
public int Convert(int i)
{
    return lookup[i];
}

有没有办法做到这一点而不污染我的类的名称空间?我可以以某种方式声明一个仅在当前范围内可见的静态数组吗?


本地数组

Roslyn 编译器将本地数组放入元数据中。让我们来看看你的第一个版本Convert method:

public int Convert(int i)
{
    int[] lookup = new[] {1, 2, 4, 8, 16, 32, 666, /*...*/ };
    return lookup[i];
}

以下是相应的 IL 代码(发布版本,Roslyn 1.3.1.60616):

// Token: 0x06000002 RID: 2 RVA: 0x0000206C File Offset: 0x0000026C
.method public hidebysig 
    instance int32 Convert (
        int32 i
    ) cil managed noinlining 
{
    // Header Size: 1 byte
    // Code Size: 20 (0x14) bytes
    .maxstack 8

    /* 0x0000026D 1D           */ IL_0000: ldc.i4.7
    /* 0x0000026E 8D13000001   */ IL_0001: newarr    [mscorlib]System.Int32
    /* 0x00000273 25           */ IL_0006: dup
    /* 0x00000274 D001000004   */ IL_0007: ldtoken   field valuetype '<PrivateImplementationDetails>'/'__StaticArrayInitTypeSize=28' '<PrivateImplementationDetails>'::'502D7419C3650DEE94B5938147BC9B4724D37F99'
    /* 0x00000279 281000000A   */ IL_000C: call      void [mscorlib]System.Runtime.CompilerServices.RuntimeHelpers::InitializeArray(class [mscorlib]System.Array, valuetype [mscorlib]System.RuntimeFieldHandle)
    /* 0x0000027E 03           */ IL_0011: ldarg.1
    /* 0x0000027F 94           */ IL_0012: ldelem.i4
    /* 0x00000280 2A           */ IL_0013: ret
} // end of method Program::Convert

这是PrivateImplementationDetails:

// Token: 0x02000003 RID: 3
.class private auto ansi sealed '<PrivateImplementationDetails>'
    extends [mscorlib]System.Object
{
    .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = (
        01 00 00 00
    )
    // Nested Types
    // Token: 0x02000004 RID: 4
    .class nested private explicit ansi sealed '__StaticArrayInitTypeSize=28'
        extends [mscorlib]System.ValueType
    {
        .pack 1
        .size 28

    } // end of class __StaticArrayInitTypeSize=28


    // Fields
    // Token: 0x04000001 RID: 1 RVA: 0x00002944 File Offset: 0x00000B44
    .field assembly static initonly valuetype '<PrivateImplementationDetails>'/'__StaticArrayInitTypeSize=28' '502D7419C3650DEE94B5938147BC9B4724D37F99' at I_00002944 // 28 (0x001c) bytes

} // end of class <PrivateImplementationDetails>

如您所见,您的lookup数组位于程序集元数据中。当您启动应用程序时,JIT 只需从元数据中获取数组内容。一个 asm 示例(Windows 10、.NET Framework 4.6.1 (4.0.30319.42000)、RyuJIT:clrjit-v4.6.1080.0、发布版本):

            int[] lookup = new[] { 1, 2, 4, 8, 16, 32, 666, /*...*/ };
00007FFEDF0A44E2  sub         esp,20h  
00007FFEDF0A44E5  mov         esi,edx  
00007FFEDF0A44E7  mov         rcx,7FFF3D1C4C62h  
00007FFEDF0A44F1  mov         edx,7  
00007FFEDF0A44F6  call        00007FFF3E6B2600  
00007FFEDF0A44FB  mov         rdx,134CF7F2944h  
00007FFEDF0A4505  mov         ecx,dword ptr [rax+8]  
00007FFEDF0A4508  lea         r8,[rax+10h]  
00007FFEDF0A450C  vmovdqu     xmm0,xmmword ptr [rdx]  
00007FFEDF0A4511  vmovdqu     xmmword ptr [r8],xmm0  
00007FFEDF0A4516  mov         r9,qword ptr [rdx+10h]  
00007FFEDF0A451A  mov         qword ptr [r8+10h],r9  
00007FFEDF0A451E  mov         r9d,dword ptr [rdx+18h]  
00007FFEDF0A4522  mov         dword ptr [r8+18h],r9d  
            return lookup[i];
00007FFEDF0A4526  cmp         esi,ecx  
            return lookup[i];
00007FFEDF0A4528  jae         00007FFEDF0A4537  
00007FFEDF0A452A  movsxd      rdx,esi  
00007FFEDF0A452D  mov         eax,dword ptr [rax+rdx*4+10h]  
00007FFEDF0A4531  add         rsp,20h  
00007FFEDF0A4535  pop         rsi  
00007FFEDF0A4536  ret  
00007FFEDF0A4537  call        00007FFF3EB57BE0  
00007FFEDF0A453C  int         3  

LegacyJIT-x64 版本:

            int[] lookup = new[] { 1, 2, 4, 8, 16, 32, 666, /*...*/ };
00007FFEDF0E41E0  push        rbx  
00007FFEDF0E41E1  push        rdi  
00007FFEDF0E41E2  sub         rsp,28h  
00007FFEDF0E41E6  mov         ebx,edx  
00007FFEDF0E41E8  mov         edx,7  
00007FFEDF0E41ED  lea         rcx,[7FFF3D1C4C62h]  
00007FFEDF0E41F4  call        00007FFF3E6B2600  
00007FFEDF0E41F9  mov         rdi,rax  
00007FFEDF0E41FC  lea         rcx,[7FFEDF124760h]  
00007FFEDF0E4203  call        00007FFF3E73CA90  
00007FFEDF0E4208  mov         rdx,rax  
00007FFEDF0E420B  mov         rcx,rdi  
00007FFEDF0E420E  call        00007FFF3E73C8B0  
            return lookup[i];
00007FFEDF0E4213  movsxd      r11,ebx  
00007FFEDF0E4216  mov         rax,qword ptr [rdi+8]  
00007FFEDF0E421A  cmp         r11,7  
00007FFEDF0E421E  jae         00007FFEDF0E4230  
00007FFEDF0E4220  mov         eax,dword ptr [rdi+r11*4+10h]  
00007FFEDF0E4225  add         rsp,28h  
00007FFEDF0E4229  pop         rdi  
00007FFEDF0E422A  pop         rbx  
00007FFEDF0E422B  ret  
00007FFEDF0E422C  nop         dword ptr [rax]  
00007FFEDF0E4230  call        00007FFF3EB57BE0  
00007FFEDF0E4235  nop  

LegacyJIT-x86 版本:

            int[] lookup = new[] { 1, 2, 4, 8, 16, 32, 666, /*...*/ };
009A2DC4  push        esi  
009A2DC5  push        ebx  
009A2DC6  mov         ebx,edx  
009A2DC8  mov         ecx,6A2C402Eh  
009A2DCD  mov         edx,7  
009A2DD2  call        0094322C  
009A2DD7  lea         edi,[eax+8]  
009A2DDA  mov         esi,5082944h  
009A2DDF  mov         ecx,7  
009A2DE4  rep movs    dword ptr es:[edi],dword ptr [esi]  
            return lookup[i];
009A2DE6  cmp         ebx,dword ptr [eax+4]  
009A2DE9  jae         009A2DF4  
009A2DEB  mov         eax,dword ptr [eax+ebx*4+8]  
009A2DEF  pop         ebx  
009A2DF0  pop         esi  
009A2DF1  pop         edi  
009A2DF2  pop         ebp  
009A2DF3  ret  
009A2DF4  call        6B9D52F0  
009A2DF9  int         3  

静态数组

现在,我们将其与第二个版本进行比较:

private static readonly int[] lookup = new[] { 1, 2, 4, 8, 16, 32, 666, /*...*/ };

public int Convert(int i)
{            
    return lookup[i];
}

IL:

// Token: 0x04000001 RID: 1
.field private static initonly int32[] lookup

// Token: 0x06000002 RID: 2 RVA: 0x00002056 File Offset: 0x00000256
.method public hidebysig 
    instance int32 Convert (
        int32 i
    ) cil managed noinlining 
{
    // Header Size: 1 byte
    // Code Size: 8 (0x8) bytes
    .maxstack 8

    /* 0x00000257 7E01000004   */ IL_0000: ldsfld    int32[] ConsoleApplication5.Program::lookup
    /* 0x0000025C 03           */ IL_0005: ldarg.1
    /* 0x0000025D 94           */ IL_0006: ldelem.i4
    /* 0x0000025E 2A           */ IL_0007: ret
} // end of method Program::Convert

// Token: 0x02000003 RID: 3
.class private auto ansi sealed '<PrivateImplementationDetails>'
    extends [mscorlib]System.Object
{
    .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = (
        01 00 00 00
    )
    // Nested Types
    // Token: 0x02000004 RID: 4
    .class nested private explicit ansi sealed '__StaticArrayInitTypeSize=28'
        extends [mscorlib]System.ValueType
    {
        .pack 1
        .size 28

    } // end of class __StaticArrayInitTypeSize=28


    // Fields
    // Token: 0x04000002 RID: 2 RVA: 0x000028FC File Offset: 0x00000AFC
    .field assembly static initonly valuetype '<PrivateImplementationDetails>'/'__StaticArrayInitTypeSize=28' '502D7419C3650DEE94B5938147BC9B4724D37F99' at I_000028fc // 28 (0x001c) bytes

} // end of class <PrivateImplementationDetails>

ASM(RyuJIT-x64):

            return lookup[i];
00007FFEDF0B4490  sub         rsp,28h  
00007FFEDF0B4494  mov         rax,212E52E0080h  
00007FFEDF0B449E  mov         rax,qword ptr [rax]  
00007FFEDF0B44A1  mov         ecx,dword ptr [rax+8]  
00007FFEDF0B44A4  cmp         edx,ecx  
00007FFEDF0B44A6  jae         00007FFEDF0B44B4  
00007FFEDF0B44A8  movsxd      rdx,edx  
00007FFEDF0B44AB  mov         eax,dword ptr [rax+rdx*4+10h]  
00007FFEDF0B44AF  add         rsp,28h  
00007FFEDF0B44B3  ret  
00007FFEDF0B44B4  call        00007FFF3EB57BE0  
00007FFEDF0B44B9  int         3  

ASM(LegacyJIT-x64):

            return lookup[i];
00007FFEDF0A4611  sub         esp,28h  
00007FFEDF0A4614  mov         rcx,226CC5203F0h  
00007FFEDF0A461E  mov         rcx,qword ptr [rcx]  
00007FFEDF0A4621  movsxd      r8,edx  
00007FFEDF0A4624  mov         rax,qword ptr [rcx+8]  
00007FFEDF0A4628  cmp         r8,rax  
00007FFEDF0A462B  jae         00007FFEDF0A4637  
00007FFEDF0A462D  mov         eax,dword ptr [rcx+r8*4+10h]  
00007FFEDF0A4632  add         rsp,28h  
00007FFEDF0A4636  ret  
00007FFEDF0A4637  call        00007FFF3EB57BE0  
00007FFEDF0A463C  nop  

ASM(LegacyJIT-x86):

            return lookup[i];
00AA2E18  push        ebp  
00AA2E19  mov         ebp,esp  
00AA2E1B  mov         eax,dword ptr ds:[03628854h]  
00AA2E20  cmp         edx,dword ptr [eax+4]  
00AA2E23  jae         00AA2E2B  
00AA2E25  mov         eax,dword ptr [eax+edx*4+8]  
00AA2E29  pop         ebp  
00AA2E2A  ret  
00AA2E2B  call        6B9D52F0  
00AA2E30  int         3  

基准测试

让我们在以下的帮助下编写一个基准测试基准网 https://github.com/PerfDotNet/BenchmarkDotNet

[Config(typeof(Config)), LegacyJitX86Job, LegacyJitX64Job, RyuJitX64Job, RPlotExporter]
public class ArrayBenchmarks
{
    private static readonly int[] lookup = new[] {1, 2, 4, 8, 16, 32, 666, /*...*/};

    [MethodImpl(MethodImplOptions.NoInlining)]
    public int ConvertStatic(int i)
    {
        return lookup[i];
    }

    [MethodImpl(MethodImplOptions.NoInlining)]
    public int ConvertLocal(int i)
    {
        int[] localLookup = new[] {1, 2, 4, 8, 16, 32, 666, /*...*/};
        return localLookup[i];
    }

    [Benchmark]
    public int Static()
    {
        int sum = 0;
        for (int i = 0; i < 10001; i++)
            sum += ConvertStatic(0);
        return sum;
    }

    [Benchmark]
    public int Local()
    {
        int sum = 0;
        for (int i = 0; i < 10001; i++)
            sum += ConvertLocal(0);
        return sum;
    }

    private class Config : ManualConfig
    {
        public Config()
        {
            Add(new MemoryDiagnoser());                
            Add(MarkdownExporter.StackOverflow);
        }
    }
}

请注意,这是一个合成玩具基准,它使用NoInlining为了Convert方法。我们用它来展示两种方法之间的差异。实际性能将取决于您如何使用Convert代码中的方法。我的结果:

Host Process Environment Information:
BenchmarkDotNet.Core=v0.9.9.0
OS=Microsoft Windows NT 6.2.9200.0
Processor=Intel(R) Core(TM) i7-4702MQ CPU 2.20GHz, ProcessorCount=8
Frequency=2143474 ticks, Resolution=466.5324 ns, Timer=TSC
CLR=MS.NET 4.0.30319.42000, Arch=64-bit RELEASE [RyuJIT]
GC=Concurrent Workstation
JitModules=clrjit-v4.6.1586.0

Type=ArrayBenchmarks  Mode=Throughput  

 Method | Platform |       Jit |        Median |     StdDev |    Gen 0 | Gen 1 | Gen 2 | Bytes Allocated/Op |
------- |--------- |---------- |-------------- |----------- |--------- |------ |------ |------------------- |
 Static |      X64 | LegacyJit |    24.0243 us |  0.1590 us |        - |     - |     - |               1.07 |
  Local |      X64 | LegacyJit | 2,068.1034 us | 33.7142 us | 1,089.00 |     - |     - |         436,603.02 |
 Static |      X64 |    RyuJit |    20.7906 us |  0.2018 us |        - |     - |     - |               1.06 |
  Local |      X64 |    RyuJit |    83.4041 us |  0.9993 us |   613.55 |     - |     - |         244,936.53 |
 Static |      X86 | LegacyJit |    20.9957 us |  0.2267 us |        - |     - |     - |               1.01 |
  Local |      X86 | LegacyJit |   167.6257 us |  1.3543 us |   431.43 |     - |     - |         172,121.77 |

结论

  • .NET 是否缓存硬编码的本地数组?有点:Roslyn 编译器将其放入元数据中。
  • 在这种情况下我们有任何开销吗?不幸的是,是的:JIT 将为每次调用从元数据中复制数组内容;它比静态数组的工作时间更长。运行时还分配对象并产生内存流量。
  • 我们应该关心它吗?这取决于。如果它是一个热门方法并且您想获得良好的性能水平,则应该使用静态数组。如果是不影响应用程序性能的冷方法,您可以probably应该编写“好的”源代码并将数组放在方法范围内。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

创建一个常量但本地的数组 的相关文章

随机推荐

  • 仅使用 CSS 使相邻同级元素具有相同的宽度

    我提前表示抱歉 因为出于保密原因 我无法显示我正在处理的代码 图像 但我认为我可以很简单地解释它 我有一个 h1 充当我的网页标题的元素 该标题可以根据用户所在的特定页面的标题更改长度 因此它可以说 主页 也可以说 已保存的项目 等 长度各
  • 特定版本的 HTC DESIRE HD 中 SQLite 中缺少表

    我的应用程序在 asset 文件夹中有一个 SQLite 数据库 当用户启动我的应用程序时 将创建数据库和表 这适用于许多设备 Nexus One Htc Magic SGS X10 甚至 Htc Desire HD v2 2 我的应用程序
  • 大 O 时间复杂度中的指数分母(分数指数)从何而来?

    In algorithm descirptions I sometimes encounter time complexities that look like O n29 20 m7 3 I see where and numerator
  • 为什么 stanford corenlp 性别识别是不确定的?

    我有以下结果 正如您所看到的 名字 edward 有不同的结果 空和男性 好几个名字都发生过这种情况 edward Gender null james Gender MALE karla Gender null edward Gender
  • ps au | 的结果grep ssh 在 Node.js(使用spawn/pipe)与 shell 中的不同

    我正在研究节点流和子进程 所以我想用管道模拟下一个 shell 命令 ps au grep ssh 所以我写了下一个代码 var spawn require child process spawn var ps spawn ps au va
  • 当浏览器不支持 JavaScript 时,如何在单击链接后执行脚本?

    我正在尝试使用如下所示的 Facebook 转换代码 其中包括script 和 noscripts 标签
  • rac_signalForSelector:需要空实现

    我有一个实现 UICollectionViewDelegate 协议的类 我正在使用 rac signalForSelector 来注册选择 如下所示 self rac signalForSelector selector collecti
  • 自定义 MapHttpAttributeRoutes 以进行 Web Api 版本控制

    我正在实施 Web API 版本控制 如下所示Web API 版本控制 http www codeproject com Articles 741326 Introduction to Web API Versioning 我的控制器位于
  • 网站部署后需要强制刷新

    部署新版本的网站后 浏览器会从旧网页的缓存中加载所有内容 直到强制刷新完成 图像是旧的 cookie 是旧的 并且一些 AJAX 部分无法工作 部署后我应该如何继续为用户提供最新版本的页面 该网页是使用IIS7 的ASP Net网页 您可以
  • 可靠地检测用户是否在网页上使用鼠标?

    在我们的页面上 我们有一些长水平滚动的 iframe div 如果用户使用鼠标 他们可以使用滚动条滚动 我们希望他们能够选择 iframe 中的文本 但是 如果他们仅使用触摸 则滚动条会很麻烦 我想在整个事物上覆盖一个透明元素 以使他们能够
  • 如何在 PostgreSQL 中生成虚拟表来生成日期序列?

    我想生成一个日期列表 希望与另一个表连接 但我不知道要使用什么语法 类似于 SELECT dates date transactions account id transactions amount FROM as dates LEFT J
  • 订票系统:数据库访问问题

    我正在创建一个巴士票预订系统 我创建了一个名为 Traveler 的数据库和两个分别名为 Useriden 和 BusDB 的表 在 aspx cs 文件 注册页面 中 我正在检查重复的用户名 但它只是导航到下一页 我已经尝试了一切 但无法
  • Rails 中的composed_of - 何时使用它?

    什么时候应该使用 ActiveRecordcomposed of http apidock com rails ActiveRecord Aggregations ClassMethods composed of类方法 就我个人而言 我认为
  • 将Java程序运行到另一个程序中[重复]

    这个问题在这里已经有答案了 可能的重复 在java程序中执行另一个jar https stackoverflow com questions 1320476 execute another jar in a java program 我尝试
  • 是否可以以编程方式向场景添加行?

    我想在每个 SpecFlow 测试的开头添加相同的行 这一行指定了几个场景的列表 这些场景会随着时间的推移而改变 因此为每个测试维护这个列表是不可行的 例如 Given I have set my site theme to
  • 发送用户 ID 和 access_token

    我正在使用 React 前端在 ASP NET Core 2 1 应用程序中实现 Auth0 一旦用户进行身份验证 我就会得到access token and an id token 我的目的很明确access token是授予对我的 AP
  • Chef-solo 从 bash 脚本获取日志

    我正在通过 Chef 执行 shell 脚本 如下所示 execute Run postgres data migration do command home ubuntu build target infra base psql10 mi
  • 单一职责原则有什么用?

    我试图理解单一职责原则 但我很难理解这个概念 我正在阅读 Lucian Paul Torje Adrian Ianculescu Kamalmeet Singh 所著的 Java 设计模式和最佳实践 一书 在这本书中我正在阅读单一职责原则章
  • Webpack 不排除 node_modules

    我正在使用 webpack 作为我正在构建的 Node 框架 尽管我应该承认 我应该使用 gulp 当我包含 EJS 模块时 webpack 将其包含在编译的源代码中 即使我明确告诉它排除 node modules 目录 module ex
  • 创建一个常量但本地的数组

    有时我需要针对单个方法的硬编码查找表 我也可以创建这样一个数组 在方法本身本地 类内静态 第一种情况的示例 public int Convert int i int lookup new 1 2 4 8 16 32 666 return l