测试8个后续字节不会转换为单个比较指令

如何解决测试8个后续字节不会转换为单个比较指令

this question的启发,我比较了三个不同的函数来检查参数所指向的8个字节是否为零(请注意,在原始问题中,字符是与'0'而不是{{1 }}):

0

尽管我期望启用优化后的汇编结果相同,但是在x64上,只有bool f1(const char *ptr) { for (int i = 0; i < 8; i++) if (ptr[i]) return false; return true; } bool f2(const char *ptr) { bool res = true; for (int i = 0; i < 8; i++) res &= (ptr[i] == 0); return res; } bool f3(const char *ptr) { static const char tmp[8]{}; return !std::memcmp(ptr,tmp,8); } 版本被转换为单个memcmp指令。 cmpf1都被转换为缠绕或未缠绕的循环。而且,这适用于所有带有f2的GCC,Clang和Intel编译器。

为什么没有-O3f1不能被优化为单个比较指令的原因?对我来说,这似乎是一个非常简单的优化。

实时演示:https://godbolt.org/z/j48366

解决方法

是否有任何原因无法将f1和f2优化为单个比较指令(可能带有额外的未对齐负载)?对我来说,这似乎是一个非常简单的优化。

f1 中,当ptr[i]为true时,循环停止,因此与其他两个函数一样直接考虑8个元素或直接比较8个元素并不总是等效的字节字,如果数组的大小小于8(编译器不知道数组的大小):

f1("\000\001"); // no access out of the array
f2("\000\001"); // access out of the array
f3("\000\001"); // access out of the array

对于 f2 ,我同意在CPU允许从任何地址对齐方式读取8字节的字的情况下,可以用8字节比较来代替,这是x64的情况,但是可以引入异常情况,如Unusual situations where this wouldn't be safe in x86 asm

中所述 ,

首先,f1在第一个非零字节处停止读取,因此在某些情况下,如果将指针传递给页面末尾附近的较短对象,它不会出错,并且下一页未映射。 @bruno指出,在f1没有遇到UB的情况下,无条件读取8个字节可能会出错。 (Is it safe to read past the end of a buffer within the same page on x86 and x64?)。编译器不知道您永远不会以这种方式使用它。它必须使代码对于任何假设的调用者都适用于每种可能的非UB情况。

您可以通过使函数arg const char ptr[static 8](但这是C99的功能,而不是C ++)来解决此问题,以确保即使C抽象机不能触摸全部8个字节也可以安全。然后,编译器可以安全地创建读取。 (指向struct {char buf[8]};的指针也可以,但是如果实际指向的对象不是那样,则严格严格地命名是安全的。)


GCC和clang无法自动向量化在第一次迭代之前未知跳闸计数的循环。这样就可以排除所有f1之类的搜索循环,即使它检查一个已知大小的静态数组。 (不过,ICC可以对某些搜索循环进行矢量化处理,例如朴素的strlen实现。)

您的f2可以像f3一样优化为qword cmp,而不必克服编译器内部的主要限制,因为它总是进行8次迭代。实际上,当前每晚do optimize f2 的c声,谢谢@Tharwen的发现。

识别循环模式并不是那么简单,并且需要花费编译时间来寻找。 IDK这种优化在实践中将具有多大的价值。这是编译器开发人员在考虑编写更多代码以查找此类模式时需要权衡的。 (代码的维护成本和编译时成本。)

该值取决于多少真实世界代码实际上具有这样的模式,以及在找到代码时可​​以节省多少。在这种情况下,这是一个很好的节省,因此clang寻找它并不疯狂,特别是如果它们具有将8字节的循环通常转换为8字节整数操作的基础结构。


实际上,如果您要使用memcmp;显然,大多数编译器不会花时间寻找f2之类的模式。现代编译器确实可靠地内联了它,尤其是对于x86-64来说,未对齐的负载在asm中是安全有效的。

或者,如果您认为编译器比memcmp更可能具有内置的memcpy,则可以使用memcpy进行别名安全的未对齐加载并进行比较。

或者在GNU C ++中,使用typedef表示未对齐的may-alias负载:

bool f4(const char *ptr) {
   typedef uint64_t aliasing_unaligned_u64 __attribute__((aligned(1),may_alias));
    auto val = *(const aliasing_unaligned_u64*)ptr;
    return val != 0;
}

Godbolt with GCC10 -O3上编译:

f4(char const*):
        cmp     QWORD PTR [rdi],0
        setne   al
        ret

投射到uint64_t*可能会违反alignof(uint64_t),并且可能违反严格混叠规则,除非char*指向的实际对象与uint64_t兼容。 / p>

是的,在x86-64上对齐确实很重要,因为ABI允许编译器基于它进行假设。在极端情况下,真正的编译器可能会发生movaps错误或其他问题。

,

您需要稍微帮助编译器来获得所需的内容...如果要在一个CPU操作中比较8个字节,则需要更改char指针,使其指向实际上是8个字节的东西长,就像一个uint64_t指针。

如果您的编译器不支持uint64_t,则可以改用unsigned long long*

#include <cstdint>

inline bool EightBytesNull(const char *ptr)
{  
    return *reinterpret_cast<const uint64_t*>(ptr) == 0;
}

请注意,这将适用于x86,但不适用于要求严格整数内存对齐的ARM。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


依赖报错 idea导入项目后依赖报错,解决方案:https://blog.csdn.net/weixin_42420249/article/details/81191861 依赖版本报错:更换其他版本 无法下载依赖可参考:https://blog.csdn.net/weixin_42628809/a
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下 2021-12-03 13:33:33.927 ERROR 7228 [ main] o.s.b.d.LoggingFailureAnalysisReporter : *************************** APPL
错误1:gradle项目控制台输出为乱码 # 解决方案:https://blog.csdn.net/weixin_43501566/article/details/112482302 # 在gradle-wrapper.properties 添加以下内容 org.gradle.jvmargs=-Df
错误还原:在查询的过程中,传入的workType为0时,该条件不起作用 &lt;select id=&quot;xxx&quot;&gt; SELECT di.id, di.name, di.work_type, di.updated... &lt;where&gt; &lt;if test=&qu
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct redisServer’没有名为‘server_cpulist’的成员 redisSetCpuAffinity(server.server_cpulist); ^ server.c: 在函数‘hasActiveC
解决方案1 1、改项目中.idea/workspace.xml配置文件,增加dynamic.classpath参数 2、搜索PropertiesComponent,添加如下 &lt;property name=&quot;dynamic.classpath&quot; value=&quot;tru
删除根组件app.vue中的默认代码后报错:Module Error (from ./node_modules/eslint-loader/index.js): 解决方案:关闭ESlint代码检测,在项目根目录创建vue.config.js,在文件中添加 module.exports = { lin
查看spark默认的python版本 [root@master day27]# pyspark /home/software/spark-2.3.4-bin-hadoop2.7/conf/spark-env.sh: line 2: /usr/local/hadoop/bin/hadoop: No s
使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams[&#39;font.sans-serif&#39;] = [&#39;SimHei&#39;] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -&gt; systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping(&quot;/hires&quot;) public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate&lt;String
使用vite构建项目报错 C:\Users\ychen\work&gt;npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-