C# 与 C++ 静态数组中静态常量列表初始化的效率

Posted

技术标签:

【中文标题】C# 与 C++ 静态数组中静态常量列表初始化的效率【英文标题】:Efficiency of static constant list initialization in C# vs C++ static arrays 【发布时间】:2019-03-25 13:33:04 【问题描述】:

我提前道歉。我的域主要是 C(和 C++)。我正在尝试在 C# 中编写类似的东西。让我用代码解释一下。

在 C++ 中,我可以使用在编译时处理并存储在 PE 文件的只读部分中的大型静态数组。例如:

typedef struct _MY_ASSOC
    const char* name;
    unsigned int value;
MY_ASSOC, *LPMY_ASSOC;

bool GetValueForName(const char* pName, unsigned int* pnOutValue = nullptr)

    bool bResult = false;
    unsigned int nValue = 0;

    static const MY_ASSOC all_assoc[] = 
        "name1", 123,
        "name2", 213,
        "name3", 1433,
        //... more to follow
        "nameN", 12837,
    ;

    for(size_t i = 0; i < _countof(all_assoc); i++)
    
        if(strcmp(all_assoc[i].name, pName) == 0)
        
            nValue = all_assoc[i].value;
            bResult = true;
            break;
        
    

    if(pnOutValue)
        *pnOutValue = nValue;

    return bResult;

在上面的例子中,static const MY_ASSOC all_assoc 的初始化不会在运行时被调用。它在编译时完全处理。

现在如果我用 C# 写类似的东西:

    public struct NameValue
    
        public string name;
        public uint value;
    

    private static readonly NameValue[] g_arrNV_Assoc = new NameValue[] 
        new NameValue()  name = "name1", value = 123 ,
        new NameValue()  name = "name2", value = 213 ,
        new NameValue()  name = "name3", value = 1433 ,
        // ... more to follow
        new NameValue()  name = "nameN", value = 12837 ,
    ;

    public static bool GetValueForName(string name, out uint nOutValue)
    
        foreach (NameValue nv in g_arrNV_Assoc)
        
            if (name == nv.name)
            
                nOutValue = nv.value;
                return true;
            
        

        nOutValue = 0;
        return false;
    

private static readonly NameValue[] g_arrNV_Assoc 行必须在宿主类初始化期间调用一次,并且针对该数组中的每个元素执行此操作!

所以我的问题是——我能否以某种方式对其进行优化,以便将存储在 g_arrNV_Assoc 数组中的数据存储在 PE 部分中而不是在运行时初始化?

附言。我希望我的术语能让 .NET 人员清楚。

【问题讨论】:

我对 c 和 c++ 一无所知,但在 c# 中,readonlyconst 意味着不同的东西。 const 在编译时被替换为它在整个代码中的值。 readonly 仅仅意味着任何变量只能在创建类型的实例时分配 - static readonly 意味着变量只能在类型初始化过程中分配,这是一个您无法控制的 clr 后台进程在你的代码中。 g_arrNV_Assoc 看起来是一个相当低效的数据结构,因为你是 foreach'ing name == nv.name 这应该是一个字典,是的,它会在第一次使用时加载一次。并且对GetValueForName 的每个后续调用都只会查找一个哈希表。我的意思是你还能做什么,除了在加载时加载一个非托管内存,并通过它迭代指针以获得你想要的结果。你到底想在这里实现什么,更好的加载时间? @TheGeneral:好吧,当然我并没有声称我的查找算法的效率。我只是在询问数组初始化部分。我选择它只是因为缺少打字。所以是的,使用字典(即使在 C++ 中)也需要时间来加载它。 为了在 C 中更有效地查找,我会按名称按字母顺序对静态数组进行排序(在源代码中,可能通过 PY 脚本),并在我的函数中使用二进制搜索算法.但正如你可以想象的那样,它需要的代码比我上面显示的要多得多,因此我选择了一个更简单的例子。但同样,那部分与我的问题无关。这与有效加载大型静态(即不变/不可变)数组有关。 (无论您在 .net 中如何称呼它:列表、字典、地图等) 【参考方案1】:

确实术语足够了,大型静态数组就可以了。

您实际上无法做任何事情来提高开箱即用的效率。

它最初会加载一次(在不同的时间取决于 .net 的版本以及是否有静态构造函数)。但是,它会在您调用它之前加载。

即使您只使用预定的大小将其创建为空,CLR 仍会将每个元素初始化为默认值,然后您必须以某种方式缓冲复制数据,而这些数据又必须从文件中加载。

问题是

与您在 C 中执行的操作相比,加载结构的默认静态数组实际上有多少开销 在应用程序的生命周期中加载时是否重要

如果这太过分了(我已经假设你已经确定了),还有哪些其他选项可能在框外可用?

您可以预先分配一块非托管内存,然后从某处读取并复制字节,然后使用指针依次访问。

您也可以像其他非托管 DLL 一样在标准 Dll、Pinvoke 中创建它。但是,我不确定您是否会在这里获得很多免费午餐,因为编组此类调用以加载您的 dll 会产生开销。

如果您的问题只是学术问题,那么这些确实是您唯一的选择。但是,如果这实际上是您遇到的性能问题,则需要尝试对其进行基准测试以进行微优化,并尝试找出适合您的方法。

无论如何,我并不自称什么都知道,也许其他人有更好的想法或更多信息。祝你好运

【讨论】:

嗯,是的,这更像是一种学术好奇心。我没有在那里加载 GB 的数据。 IDK,如果我不需要的话,我只是不喜欢浪费 CPU 周期。另外,我想我(仍在)学习 C#。所以谢谢你的解释。

以上是关于C# 与 C++ 静态数组中静态常量列表初始化的效率的主要内容,如果未能解决你的问题,请参考以下文章

C++中静态成员变量(不支持在类定义中初始化不是常量的静态数据成员)

C#图解教程 第六章 深入理解类

静态常量整数数组

C++中类的静态成员初始化

用另一个数组的值初始化一个本地静态常量数组

静态常量非整形成员变量的初始化问题