我正在尝试使用在编译时已知的固定max_size创建一些POD值的本地数组(例如double),然后读取运行时大小值(size< = max_size)并处理该数组中的第一个size元素. 问题是,当arr和size放在同一个struct / class中时,为什么编译器不会消除堆栈的读写操作,而arr和size是独立的局部变量的情况呢? 这是我的代码:
#include <cstddef>
constexpr std::size_t max_size = 64;

extern void process_value(double& ref_value);

void test_distinct_array_and_size(std::size_t size)
{
    double arr[max_size];
    std::size_t arr_size = size;

    for (std::size_t i = 0; i < arr_size; ++i)
        process_value(arr[i]);
}

void test_array_and_size_in_local_struct(std::size_t size)
{
    struct
    {
        double arr[max_size];
        std::size_t size;
    } array_wrapper;
    array_wrapper.size = size;

    for (std::size_t i = 0; i < array_wrapper.size; ++i)
        process_value(array_wrapper.arr[i]);
}

来自Clang的test_distinct_array_and_size和-O3的汇编输出:

test_distinct_array_and_size(unsigned long): # @test_distinct_array_and_size(unsigned long)
  push r14
  push rbx
  sub rsp,520
  mov r14,rdi
  test r14,r14
  je .LBB0_3
  mov rbx,rsp
.LBB0_2: # =>This Inner Loop Header: Depth=1
  mov rdi,rbx
  call process_value(double&)
  add rbx,8
  dec r14
  jne .LBB0_2
.LBB0_3:
  add rsp,520
  pop rbx
  pop r14
  ret

test_array_and_size_in_local_struct的程序集输出:

test_array_and_size_in_local_struct(unsigned long): # @test_array_and_size_in_local_struct(unsigned long)
  push r14
  push rbx
  sub rsp,520
  mov qword ptr [rsp + 512],rdi
  test rdi,rdi
  je .LBB1_3
  mov r14,rsp
  xor ebx,ebx
.LBB1_2: # =>This Inner Loop Header: Depth=1
  mov rdi,r14
  call process_value(double&)
  inc rbx
  add r14,8
  cmp rbx,qword ptr [rsp + 512]
  jb .LBB1_2
.LBB1_3:
  add rsp,520
  pop rbx
  pop r14
  ret

最新的GCC和MSVC编译器对堆栈的读写操作基本相同.

正如我们所看到的,在后一种情况下,对堆栈上的array_wrapper.size变量的读取和写入不会被优化.在循环开始之前将位值写入位置[rsp 512],并在每次迭代之后从该位置读取.

所以,编译器有点期望我们想要从process_value(array_wrapper.arr [i])调用修改array_wrapper.size(通过获取当前数组元素的地址并对它应用一些奇怪的偏移量?)

但是,如果我们试图通过该调用这样做,那不是未定义的行为吗?

当我们以下面的方式重写循环时

for (std::size_t i = 0,sz = array_wrapper.size; i < sz; ++i)
    process_value(array_wrapper.arr[i]);

,每次迭代结束时那些不必要的读取都将消失.但是对[rsp 512]的初始写入仍然存在,这意味着编译器仍然希望我们能够从这些process_value调用中访问该位置的array_wrapper.size变量(通过做一些奇怪的基于偏移的魔术).

为什么?

这是现代编译器实现中的一个小缺点(希望很快就会修复)?或者,当我们将数组及其大小放入同一个类时,C标准确实需要这样的行为导致生成效率较低的代码吗?

附:

我意识到上面的代码示例可能看起来有点人为.但请考虑一下:我想在我的代码中使用一个轻量级的boost :: container :: static_vector类模板,以便使用POD元素的伪动态数组进行更安全,更方便的“C风格”操作.所以我的PODVector将在同一个类中包含一个数组和一个size_t:

template<typename T,std::size_t MaxSize>
class PODVector
{
    static_assert(std::is_pod<T>::value,"T must be a POD type");

private:
    T _data[MaxSize];
    std::size_t _size = 0;

public:
    using iterator = T *;

public:
    static constexpr std::size_t capacity() noexcept
    {
        return MaxSize;
    }

    constexpr PODVector() noexcept = default;

    explicit constexpr PODVector(std::size_t initial_size)
        : _size(initial_size)
    {
        assert(initial_size <= capacity());
    }

    constexpr std::size_t size() const noexcept
    {
        return _size;
    }

    constexpr void resize(std::size_t new_size)
    {
        assert(new_size <= capacity());
        _size = new_size;
    }

    constexpr iterator begin() noexcept
    {
        return _data;
    }

    constexpr iterator end() noexcept
    {
        return _data + _size;
    }

    constexpr T & operator[](std::size_t position)
    {
        assert(position < _size);
        return _data[position];
    }
};

用法:

void test_pod_vector(std::size_t size)
{
    PODVector<double,max_size> arr(size);

    for (double& val : arr)
        process_value(val);
}

如果上面描述的问题确实是由C的标准强制的(并且不是编译器编写者的错误),那么这样的PODVector将永远不会像数组的原始使用和大小的“无关”变量一样高效.对于C语言而言,这对于需要零开销抽象的语言来说非常糟糕.

解决方法

这是因为void process_value(double& ref_value);通过引用接受参数.编译器/优化器假定别名,即process_value函数可以通过引用ref_value更改可访问的内存,从而更改数组后面的size成员.

编译器假定因为数组和大小是同一个对象的成员array_wrapper函数,process_value可能会将对第一个元素的引用(在第一次调用时)转换为对象的引用(并将其存储在别处)并将对象转换为unsigned char并读取或替换其整个表示.这样在函数返回后,必须从内存中重新加载对象的状态.

当size是堆栈上的独立对象时,编译器/优化器假定没有其他任何东西可能具有对它的引用/指针并将其缓存在寄存器中.

在Chandler Carruth: Optimizing the Emergent Structures of C++中,他解释了为什么优化器在调用接受引用/指针参数的函数时会遇到困难.仅在绝对必要时才使用引用/指针函数参数.

如果您想更改该值,则性能更高的选项是:

double process_value(double value);

然后:

array_wrapper.arr[i] = process_value(array_wrapper.arr[i]);

此更改导致optimal assembly:

.L23:
movsd xmm0,QWORD PTR [rbx]
add rbx,8
call process_value2(double)
movsd QWORD PTR [rbx-8],xmm0
cmp rbx,rbp
jne .L23

要么:

for(double& val : arr)
    val = process_value(val);

为什么C编译器不会优化对struct数据成员的读写,而不是不同的局部变量?的更多相关文章

  1. ios – 如何从变量访问属性或方法?

    是否可以使用变量作为Swift中方法或属性的名称来访问方法或属性?在PHP中,您可以使用$object->{$variable}.例如编辑:这是我正在使用的实际代码:解决方法你可以做到,但不能使用“纯粹的”Swift.Swift的重点是防止这种危险的动态属性访问.你必须使用Cocoa的Key-ValueCoding功能:非常方便,它完全穿过你要穿过的字符串到属性名称的桥,但要注意:这里是龙.

  2. iOS &gt;&gt;块&gt;&gt;更改块外部的变量值

    我不是在处理一个Object并改变它,就像我的mString一样.我希望’center’属性的行为类似于myInt,因为它是直接访问的C结构,而不是指向对象的指针.我希望’backgroundColor’的行为类似于我的imstring,因为它是一个指向一个新对象的对象的指针,不是吗?

  3. ios – Xcode Bot:如何在post触发器脚本上获得.ipa路径?

    我正在使用机器人来存档iOS应用程序,我需要获取.ipa产品路径才能将其发布到我们的分发系统中.机器人设置:并使用脚本打印所有env变量,其中不包含ipa文件的路径.此外,一些变量指向不存在的目录,即:XCS_OUTPUT_DIR这里的env变量输出:除此之外,我还能够确认.ipa文件是在另一个文件夹中创建的(/IntegrationAssets//

  4. ios – 使用附加字符串本地化Info.plist变量

    我正在尝试本地化应用程序的名称,同时仍然能够根据构建配置追加字符串.所以目前它被设置为:该设置定义为:通过这种方式,我们可以为应用程序添加后缀以用于不同的beta版本.问题是,当我们尝试本地化本地化的InfoPlist.strings中的应用程序显示名称时,就像这样我们覆盖存储在Info.plist中的值,并丢失后缀字符.这有什么好办法吗?

  5. ios – Swift编译器是否忽略未使用的函数?

    Swift编译器是编译未使用的函数还是忽略它们?

  6. iOS – 开始iOS教程 – 变量之前的下划线?

    这是正确的还是我做错了什么?

  7. ios – 静态计算变量被多次实例化

    我有一个日期格式化程序,我试图在UITableViewCell子类中创建一个单例,所以我创建了一个这样的计算属性:问题是我不止一次看到print语句,这意味着它不止一次被创建.我已经找到了其他方法,但我很想知道这里发生了什么.有任何想法吗?解决方法您的代码段相当于只获取属性,基本上它与以下内容相同:如果你只想运行一次,你应该像定义一个惰性属性一样定义它:

  8. ios – UIApplication.delegate必须仅在主线程中使用[复制]

    我应该在主调度中的viewControllers中声明这些)变量位置声明定义了它的范围.您需要确定这些变量的范围.您可以将它们声明为项目或应用程序级别(全局),类级别或特定此功能级别.如果要在其他ViewControllers中使用这些变量,则使用公共/开放/内部访问控制将其声明为全局或类级别.

  9. ios – 无法理解Objective-C块文档

    为什么localVariable“按价值使用?”>如果我在第二个例子中将__block存储类型添加到localVariable,我错误地假设该块关闭了变量,所以它将它保留在堆中直到块被释放?解决方法Howexactlyisoneexample“accessedbyreference”whiletheotheroneisaccessedbyvariable?self是当前正在执行找到块的方法的对象.强引用只是意味着对象的保留计数增加.IfIaddthe__blockstoragetypetolocalVar

  10. ios – 为BOOL变量编写getter和setter

    显然,使用obj-c,通常没有理由编写getter和setter(感谢有用的mr@synthesize).所以现在,需要做到这一点,我遇到了一个我不知道如何编写它们的问题.:p我敢肯定我可能不会以正确的方式解决我的问题–只是将我的对象子类化得更容易–但我正在尝试编写类别代码以添加属性,因为(在开头)它更快,因为我想学习如何在我的应用程序中使用类别代码.我有这个:我在setter中没有if查询就试过

随机推荐

  1. 从C到C#的zlib(如何将byte []转换为流并将流转换为byte [])

    我的任务是使用zlib解压缩数据包(已接收),然后使用算法从数据中生成图片好消息是我在C中有代码,但任务是在C#中完成C我正在尝试使用zlib.NET,但所有演示都有该代码进行解压缩(C#)我的问题:我不想在解压缩后保存文件,因为我必须使用C代码中显示的算法.如何将byte[]数组转换为类似于C#zlib代码中的流来解压缩数据然后如何将流转换回字节数组?

  2. 为什么C标准使用不确定的变量未定义?

    垃圾价值存储在哪里,为什么目的?解决方法由于效率原因,C选择不将变量初始化为某些自动值.为了初始化这些数据,必须添加指令.以下是一个例子:产生:虽然这段代码:产生:你可以看到,一个完整的额外的指令用来移动1到x.这对于嵌入式系统来说至关重要.

  3. 如何使用命名管道从c调用WCF方法?

    更新:通过协议here,我无法弄清楚未知的信封记录.我在网上找不到任何例子.原版的:我有以下WCF服务我输出添加5行,所以我知道服务器是否处理了请求与否.我有一个.NET客户端,我曾经测试这一切,一切正常工作预期.现在我想为这个做一个非托管的C客户端.我想出了如何得到管道的名称,并写信给它.我从here下载了协议我可以写信给管道,但我看不懂.每当我尝试读取它,我得到一个ERROR_broKEN_P

  4. “这”是否保证指向C中的对象的开始?

    我想使用fwrite将一个对象写入顺序文件.班级就像当我将一个对象写入文件时.我正在游荡,我可以使用fwrite(this,sizeof(int),2,fo)写入前两个整数.问题是:这是否保证指向对象数据的开始,即使对象的最开始可能存在虚拟表.所以上面的操作是安全的.解决方法这提供了对象的地址,这不一定是第一个成员的地址.唯一的例外是所谓的标准布局类型.从C11标准:(9.2/20)Apointe

  5. c – 编译单元之间共享的全局const对象

    当我声明并初始化一个const对象时.两个cpp文件包含此标头.和当我构建解决方案时,没有链接错误,你会得到什么如果g_Const是一个非const基本类型!PrintInUnit1()和PrintInUnit2()表明在两个编译单元中有两个独立的“g_Const”具有不同的地址,为什么?

  6. 什么是C名称查找在这里? (&amp;GCC对吗?)

    为什么在第三个变体找到func,但是在实例化的时候,原始变体中不合格查找找不到func?解决方法一般规则是,任何不在模板定义上下文中的内容只能通过ADL来获取.换句话说,正常的不合格查找仅在模板定义上下文中执行.因为在定义中间语句时没有声明func,并且func不在与ns::type相关联的命名空间中,所以代码形式不正确.

  7. c – 在输出参数中使用auto

    有没有办法在这种情况下使用auto关键字:当然,不可能知道什么类型的.因此,解决方案应该是以某种方式将它们合并为一个句子.这可用吗?解决方法看起来您希望默认初始化给定函数期望作为参数的类型的对象.您无法使用auto执行此操作,但您可以编写一个特征来提取函数所需的类型,然后使用它来声明您的变量:然后你就像这样使用它:当然,只要你重载函数,这一切都会失败.

  8. 在C中说“推动一切浮动”的确定性方式

    鉴于我更喜欢将程序中的数字保留为int或任何内容,那么使用这些数字的浮点数等效的任意算术最方便的方法是什么?说,我有我想写通过将转换放在解析的运算符树叶中,无需将表达式转化为混乱是否可以使用C风格的宏?应该用新的类和重载操作符完成吗?解决方法这是一个非常复杂的表达.更好地给它一个名字:现在当您使用整数参数调用它时,由于参数的类型为double,因此使用常规的算术转换将参数转换为double用C11lambda……

  9. objective-c – 如何获取未知大小的NSArray的第一个X元素?

    在objectiveC中,我有一个NSArray,我们称之为NSArray*largeArray,我想要获得一个新的NSArray*smallArray,只有第一个x对象…

  10. c – Setprecision是混乱

    我只是想问一下setprecision,因为我有点困惑.这里是代码:其中x=以下:方程的左边是x的值.1.105=1.10应为1.111.115=1.11应为1.121.125=1.12应为1.131.135=1.14是正确的1.145=1.15也正确但如果x是:2.115=2.12是正确的2.125=2.12应为2.13所以为什么在一定的价值是正确的,但有时是错误的?请启发我谢谢解决方法没有理由期望使用浮点系统可以正确地表示您的帖子中的任何常量.因此,一旦将它们存储在一个双变量中,那么你所拥有的确切的一

返回
顶部