字符串长度函数不稳定

如何解决字符串长度函数不稳定

所以我不久前做了这个,一切看起来都很好。但是我开始注意到我的代码库中的错误,一段时间后我将其追溯到这个 strlen 函数。我使用 SIMD 指令来编写它,而且我是编写内在函数的新手,所以代码可能也不是最好的。

功能如下:

inline size_t strlen(const char* data) {
        const __m256i terminationCharacters = _mm256_setzero_si256();
        const size_t shiftAmount = ((size_t)&data) & 31;
        const __m256i* pointer = (const __m256i*) (data - shiftAmount);

        size_t length = 0;

        for (;; length += 32,++pointer) {
            const __m256i comparingData = _mm256_load_si256(pointer);
            const __m256i comparison = _mm256_cmpeq_epi8(comparingData,terminationCharacters);

            if (!_mm256_testc_si256(terminationCharacters,comparison)) {
                const auto mask = _mm256_movemask_epi8(comparison);

                return length + _tzcnt_u32(mask >> shiftAmount);
            }
        }
    }

解决方法

您尝试将启动处理合并到对齐向量循环中至少有 2 个显示错误:

  • 如果对齐的加载找到任何零字节,则退出循环,即使它们来自字符串的正确开头之前。 (@James Griffin 在评论中发现了这一点)。您需要执行 mask >>= shiftAmount 并检查非零值,以查看在字符串开头之后的加载部分中是否有任何匹配项。 (不要使用 _mm256_testc_si256,只需移动掩码并检查)。

  • _tzcnt_u32(mask >> shiftAmount); 对于 之后的任何向量都是错误的。整个向量来自字符串开头之后的字节,因此您需要 tzcnt 来查看所有位。相反,我认为您想要 _tzcnt_u32(mask) - shiftAmount

在实际字符串之前但在第一个对齐的向量内使用 0 字节制作一些测试用例。测试用例的最终 0 位于相对于向量的不同位置,并且非零并针对 libc strlen 测试您的版本。 (甚至可能是前 32 个字节内的一些随机 0 位置,然后在此后的前 64 个字节内。)

如果您将其与循环分开,那么您处理未对齐启动的策略应该有效。 (Is it safe to read past the end of a buffer within the same page on x86 and x64?)。

另一个选项是在从字符串的实际开始加载第一个未对齐的向量之前进行页面交叉检查。 (但是你需要回退到别的东西)。然后对齐:重叠很好;只要正确计算最终长度,两次检查同一个字节是否为零都没有关系。


(您也真的不希望编译器在循环内浪费指令来增加一个指针一个单独的长度,因此请检查生成的 asm。循环后的指针减法应该做诀窍。甚至投射到uintptr_t
此外,您可以从初始函数 arg 中减去最终的零位置,而不是从对齐的指针中减去,因此不是减去 shiftAmount 两次,而是除了初始对齐之外根本不使用它。)

根本不要使用 vptest 内部函数 (_mm256_testc_si256),即使在您应该检查所有字节的主循环中; _mm_cmp* 结果并不是更好。 vptest 是 2 uop,不能与分支指令进行宏融合。但是 vpmovmskb eax,ymm0 是 1 uop,而 test eax,eax / jz .loop 是另一个宏融合的 uop。更妙的是,您实际上需要循环后的整数 movemask 结果,所以您已经有了它。


相关

  • Is it safe to read past the end of a buffer within the same page on x86 and x64?

  • Why does glibc's strlen need to be so complicated to run quickly?(包括指向 glibc 的 strlen 实现的手写 x86-64 asm 的链接。)除非您使用的平台具有较差的 C 库,否则通常您应该使用它,因为 glibc 使用动态链接期间的 CPU 检测,为您的 CPU 选择一个好的 strlen(和 memcpy 等)版本。 strlen 的未对齐启动有点棘手,我认为 glibc 做出了合理的选择,除非函数调用开销是一个大问题。它还具有针对大字符串的良好循环展开技术(例如 _mm256_min_epu8,如果 2 个输入向量中的任何一个具有零,则在向量元素中获得零,因此它可以在整个缓存中分摊实际的移动掩码/分支工作- 数据行)。不过,对于中等长度的字符串来说,它可能过于激进。

    请注意,glibc 的许可证是 LGPL,因此除非您的许可证兼容,否则您不能将代码从 glibc 复制到您的项目中。甚至编写与其 asm 等效的内在函数也可能存在问题。

  • Why is this code using strlen heavily 6.5x slower with GCC optimizations enabled? - 一个简单的 SSE2 strlen,它在手写 asm 中处理错位。以及对基准测试的评论。

  • https://agner.org/optimize/ - 指南和指令表,他的子程序库(手写 asm)包括一个 strlen。 (但请注意,它已获得 GPL 许可。)

我假设某些 BSD 和 MacOS 在更宽松的许可下具有 asm strlen,如果您的项目不兼容 GPL,您可以使用/查看。

,

无意冒犯

size_t strlen(char *p)
{
    size_t ret_val = 0;

    while (*p++) ret_val++;

    retirn ret_val;
}

很久以前就做得很好。此外,今天的优化编译器为它提供了非常紧凑的代码,您的代码无法阅读。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 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时,该条件不起作用 <select id="xxx"> SELECT di.id, di.name, di.work_type, di.updated... <where> <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,添加如下 <property name="dynamic.classpath" value="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['font.sans-serif'] = ['SimHei'] # 能正确显示负号 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 -> 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("/hires") 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<String
使用vite构建项目报错 C:\Users\ychen\work>npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-