- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
我想使用https://research.swtch.com/sparse中描述的技巧在C++中构建一个密集整数集。通过允许其自身读取未初始化的内存,此方法可实现良好的性能。
如何在不触发未定义行为且不运行诸如Valgrand或ASAN之类的工具的情况下实现此数据结构?
编辑:似乎响应者专注于“未初始化”一词,并在语言标准的上下文中对其进行解释。在我看来,这可能是一个较差的词选择-这里的“未初始化”仅意味着其值对于算法的正确运行并不重要。显然有可能安全地实现此数据结构(LLVM在SparseMultiSet中做到了)。我的问题是,最好,最高效的方法是什么?
最佳答案
我可以看到四种基本方法。它们不仅适用于C++,而且适用于大多数其他低级语言(例如C),它们使未初始化的访问成为可能,但不允许,最后一种语言甚至适用于高级“安全”语言。
忽略标准,以通常的方式实现
这是律师疯狂讨厌的一招!不过,请不要害怕-遵循该解决方案的解决方案不会违反规则,因此,如果您属于规则贴纸的一类,则可以跳过此部分。
该标准充分利用了未定义的值,并且它允许的一些漏洞(例如,将一个未定义的值复制到另一个值)并不能真正给您足够的绳索来实际实现您想要的功能-即使在C中,限制也略小(例如,参见涵盖C11的this answer,它解释了访问indeterminiate
值可能不会直接触发UB时,任何结果也是不确定的,并且该值实际上似乎在访问之间是偶然的。
因此,您只要知道当前大多数或所有编译器都会将其编译为预期的代码,并且知道您的代码不符合标准,便可以实现它。
至少在my test中,所有gcc
,clang
和icc
都没有利用非法访问来做任何疯狂的事情。当然,该测试并不全面,即使您可以构建一个测试,其行为也可能在新版本的编译器中发生变化。
如果访问未初始化内存的方法的实现是在一个单独的编译单元中编译一次的,那将是最安全的-这可以很容易地检查它是否做对了(只需检查一下汇编),并且几乎不可能( LTGC之外),使编译器做任何棘手的事情,因为它无法证明是否正在访问未初始化的值。
不过,从理论上讲,这种方法还是不安全的,您应该非常仔细地检查编译后的输出,并采取其他措施。
如果采用这种方法,像valgrind
这样的工具很可能会报告未初始化的读取错误。
现在这些工具可以在汇编级别运行,并且一些未初始化的读取可能很好(例如,参见快速标准库实现的下一项),因此它们实际上并没有立即报告未初始化的读取,而是具有多种启发式以确定是否实际使用了无效值。例如,他们可以避免报告错误,直到他们确定未初始化的值用于确定条件跳转的方向,或者根据启发式方法无法跟踪/恢复的某些其他 Action 。您可能能够使编译器发出可读取未初始化内存的代码,但根据此启发式方法是安全的。
更有可能,您将无法执行此操作(由于此处的逻辑非常微妙,因为它依赖于两个数组中值的关系),因此您可以在选择的工具中使用抑制选项来隐藏错误。例如,valgrind可以基于stack trace进行抑制-实际上,默认情况下已经有许多抑制条目用于隐藏各种标准库中的假阳性。
由于它基于堆栈跟踪工作,因此如果读取以内联代码进行,您可能会遇到困难,因为每个调用站点的堆栈顶部都会不同。您可以避免这种情况,以确保未内联函数。
使用组装
标准中定义不明确的内容通常在组装级别定义明确。这就是为什么编译器和标准库通常可以以比使用C或C++更快的方式实现事物的原因:用汇编语言编写的libc
例程已经针对特定的体系结构,而不必担心代码中的各种警告。语言规范可以使事情在各种硬件上快速运行。
通常,在汇编中实现任何数量的代码都是一项昂贵的工作,但是在这里这只是少数,因此根据您要定位的平台数量,这可能是可行的。您甚至根本不需要自己编写方法-只需编译C++版本(或使用godbolt并复制程序集即可。is_member
函数,例如example1,如下所示:
sparse_array::is_member(unsigned long):
mov rax, QWORD PTR [rdi+16]
mov rdx, QWORD PTR [rax+rsi*8]
xor eax, eax
cmp rdx, QWORD PTR [rdi]
jnb .L1
mov rax, QWORD PTR [rdi+8]
cmp QWORD PTR [rax+rdx*8], rsi
sete al
calloc
魔术
calloc
2,则从基础分配器显式请求零内存。现在,正确版本的
calloc
可以简单地调用
malloc
,然后将返回的内存清零,但是实际的实现依赖于以下事实:操作系统级别的内存分配例程(
sbrk
和
mmap
,差不多)通常会在任何操作系统上将内存归零具有 protected 内存(即所有大内存),以避免再次将内存清零。
mmap
的调用来满足此要求。当(如果有的话)写入内存时,写时复制实际上会分配一个新页面。因此,由于操作系统已经需要将页面清零,因此可以免费分配较大的清零内存区域。
calloc
之上实现稀疏集可能与名义上未初始化的版本一样快,同时又安全又符合标准。
calloc
的行为符合预期。优化的行为通常仅在您的程序大约“预先”初始化大量长生命周期的归零内存时才会发生。也就是说,用于优化calloc的典型逻辑如下:
calloc(N)
if (can satisfy a request for N bytes from allocated-then-freed memory)
memset those bytes to zero and return them
else
ask the OS for memory, return it directly because it is zeroed
malloc
基础结构(也是
new
和 friend 的基础)有一个(可能为空)内存池,该内存池已从OS那里请求,通常尝试首先分配给该内存。这个池由来自操作系统的最后一个块请求的内存组成,但是没有分发(例如,因为用户请求了32个字节,但是分配的内存要求从OS中以1 MB的块为块,所以还剩下很多),以及分配给进程但随后通过
free
或
delete
或其他方式返回的内存。该池中的内存具有任意值,并且如果可以从该池中满足
calloc
的要求,那么就不会有魔力,因为必须进行零初始化。
sparse_set
对象,则通常只从内部
malloc
池中提取内容,并将支付清零费用。如果您有一个生命周期很长的
sparse_set
对象,它占用大量内存,则可能是通过询问操作系统来分配它们的,并且您几乎免费获得了调零功能。
calloc
行为(实际上,在您的OS上或使用分配器,甚至可能没有以这种方式进行优化),通常可以通过手动映射
/dev/zero
来复制行为供您分配。在提供此功能的操作系统上,这可以确保您获得“便宜”的行为。
sparse
数组中该颗粒的初始化状态。
int32_t
值):您需要1位来跟踪每4个元素* 4个字节/元素* 8位/byte,这在分配的内存中的开销不到1%3。
sparse
之前检查此数组中的相应位即可。这会增加访问
sparse
数组的少量开销,但不会改变整体复杂性,并且检查仍然相当快。
is_member
函数现在为
looks like:
bool sparse_set::is_member(size_t i){
bool init = is_init[i >> INIT_SHIFT] & (1UL << (i & INIT_MASK));
return init && sparse[i] < n && dense[sparse[i]] == i;
}
mov rax, QWORD PTR [rdi+24]
mov rdx, rsi
shr rdx, 8
mov rdx, QWORD PTR [rax+rdx*8]
xor eax, eax
bt rdx, rsi
jnc .L2
...
is_member
是该方法最差的情况,因为某些功能(例如
clear
)根本不受影响,而其他功能(例如例如
iterate
)可以批处理
is_init
检查,并且每个
INIT_COVERAGE
元素仅执行一次(这意味着示例值的开销将再次为〜1%)。
is_init
检查将失败,并且通常会缩短其余代码,在这种情况下,您可以使用一个工作集它比
sparse
数组的大小小得多(使用示例粒度为256倍),因此可以大大减少DRAM或外部缓存级别的丢失。
is_init
初始化成本。您可以想出一个公式,在简单的情况下可以找到最佳大小-但是行为也取决于值和其他因素的“聚类”。最后,在不同的工作负载下使用动态粒度来覆盖您的基础是完全合理的-但这是以可变类次为代价的。
calloc
和lazy init解决方案之间存在相似之处:两者都根据需要懒惰地初始化内存块,但是
calloc
解决方案通过带有页表和TLB条目的MMU魔术在硬件中隐式地跟踪此内存块,而lazy init解决方案在软件中完成,并带有位图,该位图可明确跟踪已分配的颗粒。
dense
数组的惰性方法:只需将
sparse
数组与
mmap
作为
PROT_NONE
一起分配,因此每当您从
sparse
数组中的未分配页面。您发现了故障,并在
sparse
数组中为页面分配了一个指示每个元素“不存在”的标记值。
... && dense[sparse[i]] == i
检查。
i
是否大于
MAX_ELEM
-根据您的用例,您可能要检查一下。我的实现使用了
MAX_ELEM
的模板参数,这可能会导致代码速度稍快,但也会导致膨胀,并且最好也将max size设为类成员。
calloc
或执行等效的零填充优化的条件,但是根据我的测试,更多惯用的C++方法(例如
new int[size]()
)只需要在分配之后加上
memset
即可。
gcc
确实优化了
malloc
,然后将
memset
转换为
calloc
,但是如果您还是想避免使用C例程,那将没有用!
sparse
数组的每128位。
关于c++ - 如何安全地实现 “Using Uninitialized Memory For Fun And Profit”?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43789972/
#include using namespace std; class C{ private: int value; public: C(){ value = 0;
这个问题已经有答案了: What is the difference between char a[] = ?string?; and char *p = ?string?;? (8 个回答) 已关闭
关闭。此题需要details or clarity 。目前不接受答案。 想要改进这个问题吗?通过 editing this post 添加详细信息并澄清问题. 已关闭 7 年前。 此帖子已于 8 个月
除了调试之外,是否有任何针对 c、c++ 或 c# 的测试工具,其工作原理类似于将独立函数复制粘贴到某个文本框,然后在其他文本框中输入参数? 最佳答案 也许您会考虑单元测试。我推荐你谷歌测试和谷歌模拟
我想在第二台显示器中移动一个窗口 (HWND)。问题是我尝试了很多方法,例如将分辨率加倍或输入负值,但它永远无法将窗口放在我的第二台显示器上。 关于如何在 C/C++/c# 中执行此操作的任何线索 最
我正在寻找 C/C++/C## 中不同类型 DES 的现有实现。我的运行平台是Windows XP/Vista/7。 我正在尝试编写一个 C# 程序,它将使用 DES 算法进行加密和解密。我需要一些实
很难说出这里要问什么。这个问题模棱两可、含糊不清、不完整、过于宽泛或夸夸其谈,无法以目前的形式得到合理的回答。如需帮助澄清此问题以便重新打开,visit the help center . 关闭 1
有没有办法强制将另一个 窗口置于顶部? 不是应用程序的窗口,而是另一个已经在系统上运行的窗口。 (Windows, C/C++/C#) 最佳答案 SetWindowPos(that_window_ha
假设您可以在 C/C++ 或 Csharp 之间做出选择,并且您打算在 Windows 和 Linux 服务器上运行同一服务器的多个实例,那么构建套接字服务器应用程序的最明智选择是什么? 最佳答案 如
你们能告诉我它们之间的区别吗? 顺便问一下,有什么叫C++库或C库的吗? 最佳答案 C++ 标准库 和 C 标准库 是 C++ 和 C 标准定义的库,提供给 C++ 和 C 程序使用。那是那些词的共同
下面的测试代码,我将输出信息放在注释中。我使用的是 gcc 4.8.5 和 Centos 7.2。 #include #include class C { public:
很难说出这里问的是什么。这个问题是含糊的、模糊的、不完整的、过于宽泛的或修辞性的,无法以目前的形式得到合理的回答。如需帮助澄清此问题以便重新打开它,visit the help center 。 已关
我的客户将使用名为 annoucement 的结构/类与客户通信。我想我会用 C++ 编写服务器。会有很多不同的类继承annoucement。我的问题是通过网络将这些类发送给客户端 我想也许我应该使用
我在 C# 中有以下函数: public Matrix ConcatDescriptors(IList> descriptors) { int cols = descriptors[0].Co
我有一个项目要编写一个函数来对某些数据执行某些操作。我可以用 C/C++ 编写代码,但我不想与雇主共享该函数的代码。相反,我只想让他有权在他自己的代码中调用该函数。是否可以?我想到了这两种方法 - 在
我使用的是编写糟糕的第 3 方 (C/C++) Api。我从托管代码(C++/CLI)中使用它。有时会出现“访问冲突错误”。这使整个应用程序崩溃。我知道我无法处理这些错误[如果指针访问非法内存位置等,
关闭。这个问题不符合Stack Overflow guidelines .它目前不接受答案。 我们不允许提问寻求书籍、工具、软件库等的推荐。您可以编辑问题,以便用事实和引用来回答。 关闭 7 年前。
已关闭。此问题不符合Stack Overflow guidelines 。目前不接受答案。 要求我们推荐或查找工具、库或最喜欢的场外资源的问题对于 Stack Overflow 来说是偏离主题的,因为
我有一些 C 代码,将使用 P/Invoke 从 C# 调用。我正在尝试为这个 C 函数定义一个 C# 等效项。 SomeData* DoSomething(); struct SomeData {
这个问题已经有答案了: Why are these constructs using pre and post-increment undefined behavior? (14 个回答) 已关闭 6
我是一名优秀的程序员,十分优秀!