检查char数组中前导字符的最快方法是什么? 1编译器可以在调用该函数的地方内联该函数吗? 2不计算事物要比更有效地计算事物更快 3是n_zero很小还是更好? 4按位运算在这里有帮助吗?

如何解决检查char数组中前导字符的最快方法是什么? 1编译器可以在调用该函数的地方内联该函数吗? 2不计算事物要比更有效地计算事物更快 3是n_zero很小还是更好? 4按位运算在这里有帮助吗?

我在代码中遇到了瓶颈,所以这个问题的主要问题是性能。

我有一个十六进制校验和,我想检查一个字符数组的前导零。这就是我正在做的:

bool starts_with (char* cksum_hex,int n_zero) {
  bool flag {true};
  for (int i=0; i<n_zero; ++i)
    flag &= (cksum_hex[i]=='0');
  return flag;
}

如果cksum_hexn_zero前导零,则上述函数返回true。但是,对于我的应用程序,此功能非常昂贵(占总时间的60%)。换句话说,这是我的代码的瓶颈。所以我需要改善它。

我还检查了std::string::starts_with(在C ++ 20中可用),但我发现性能没有差异:

// I have to convert cksum to string
std::string cksum_hex_s (cksum_hex);
cksum_hex_s.starts_with("000");     // checking for 3 leading zeros

有关更多信息,我使用的是g++ -O3 -std=c++2a,我的gcc版本是9.3.1。

问题

  • 检查char数组中前导字符的更快方法是什么?
  • 使用std::string::starts_with可以更有效地做到这一点吗?
  • 按位运算在这里有帮助吗?

解决方法

如果您修改函数以使其尽早返回

dispatch

在获得大bool starts_with (char* cksum_hex,int n_zero) { for (int i=0; i<n_zero; ++i) { if (cksum_hex[i] != '0') return false; } return true; } n_zero的情况下,速度会更快。否则,也许您可​​以尝试分配全局字符数组false并使用'0'

std::memcmp

这里的问题是,您需要假设// make it as big as you need constexpr char cmp_array[4] = {'0','0','0'}; bool starts_with (char* cksum_hex,int n_zero) { return std::memcmp(cksum_hex,cmp_array,n_zero) == 0; } 的最大可能值。

Live example

===编辑===

考虑到抱怨没有任何分析数据来证明建议的方法的合理性,请按以下步骤操作:

使用的数据:

memcmp

const char* cs1 = "00000hsfhjshjshgj"; const char* cs2 = "20000hsfhjshjshgj"; const char* cs3 = "0000000000hsfhjshjshgj"; const char* cs4 = "0000100000hsfhjshjshgj"; 在所有情况下都是最快的,但memcmp具有提前返回暗示。

Early return vs memcmp memcmp vs orig

,

大概还拥有二进制校验和?,而不是先将其转换为ASCII文本,而是查看4*n高位以直接检查{{1} }},而不是检查n个字节是否等于0

例如如果您将哈希(或哈希的高8个字节)作为n'0',则将其右移以仅保留高uint64_t个半字节。

我展示了一些示例,说明当两个输入都是运行时变量时,它们如何针对x86-64进行编译,但它们也可以很好地编译至其他ISA(如AArch64)。这段代码都是可移植的ISO C ++。


unsigned __int128

clang使用n在x86-64上做得很好,以启用BMI1 / BMI2

bool starts_with (uint64_t cksum_high8,int n_zero)
{
    int shift = 64 - n_zero * 4;       // A hex digit represents a 4-bit nibble
    return (cksum_high8 >> shift) == 0;
}

这甚至适用于-O3 -march=haswell(shift = 0)来测试所有64位。 high_zero_nibbles(unsigned long,int): shl esi,2 neg sil # x86 shifts wrap the count so 64 - c is the same as -c shrx rax,rdi,rsi # BMI2 variable-count shifts save some uops. test rax,rax sete al ret 无法测试所有位都失败;它会通过将n=16移位> =其宽度来移位,从而遇到UB。 (在像x86这样的ISA封装了越界移位计数,用于其他移位计数的代码源将导致检查所有16位。只要在编译时看不到UB ...)希望您'仍然不打算用n_zero = 0来称呼它。

其他选项:创建仅保留高uint64_t位的掩码,如果准备晚于n_zero=0,则可以缩短通过n*4的关键路径。特别是如果cksum_high8是内联后的编译时常量,则其速度可能与检查n_zero一样快。 (例如x86-64 n_zero。)

cksum_high8 == 0

或使用位扫描功能来计数前导零位并比较test reg,immediate。不幸的是,ISO C ++ until C++20 <bit>bool high_zero_nibbles_v2 (uint64_t cksum_high8,int n_zero) { int shift = 64 - n_zero * 4; // A hex digit represents a 4-bit nibble uint64_t low4n_mask = (1ULL << shift) - 1; return cksum_high8 & ~low4n_mask; } 最终才可移植地公开了这种已经存在了数十年的通用CPU功能(例如386 >= 4*n / countl_zero);在此之前,只能用作GNU C bsf之类的编译器扩展。

如果您想知道有多少个且没有一个特定的临界阈值,那就太好了。

bsr

编译为(对于Haswell来说是c语):

__builtin_clz

所有这些指令在Intel和AMD上都很便宜,lzcnt和shl之间甚至还有一些指令级并行性。

See asm output for all 4 of these on the Godbolt compiler explorer。 Clang将1和2编译为相同的asm。使用bool high_zero_nibbles_lzcnt (uint64_t cksum_high8,int n_zero) { // UB on cksum_high8 == 0. Use x86-64 BMI1 _lzcnt_u64 to avoid that,guaranteeing 64 on input=0 return __builtin_clzll(cksum_high8) > 4*n_zero; } #include <bit> bool high_zero_nibbles_stdlzcnt (uint64_t cksum_high8,int n_zero) { return std::countl_zero(cksum_high8) > 4*n_zero; } 的两种方式都相同。否则,对于C ++ 20版本(不是UB),它需要不遗余力地处理input = 0的high_zero_nibbles_lzcnt(unsigned long,int): lzcnt rax,rdi shl esi,2 cmp esi,eax setl al # FLAGS -> boolean integer return value ret 极端情况。


要将这些扩展到更宽的哈希值,可以检查uint64_t的高位是否为全零,然后进行下一个uint64_t块。


使用SSE2与字符串-march=haswell-> bsr上的pcmpeqb进行比较,可以找到前pmovmskb位的位置,因此可以找到多少前导{ 1}}字符(如果有的话)包含在字符串表示中。因此,x86 SIMD可以非常有效地执行此操作,您可以通过内部函数从C ++使用它。

,

您可以使零缓冲区足够大,而不是与memcmp进行比较。

const char *zeroBuffer = "000000000000000000000000000000000000000000000000000";

if (memcmp(zeroBuffer,cksum_hex,n_zero) == 0) {
   // ...
}
,

要检查的内容以使您的应用程序更快:

1。编译器可以在调用该函数的地方内联该函数吗?

要么在头文件中声明该函数为内联函数,要么将该定义放在使用它的编译单元中。

2。不计算事物要比更有效地计算事物更快

是否需要所有对该函数的调用?高成本通常是在高频环路内或在昂贵的算法中称为函数的标志。您通常可以通过优化外部算法来减少调用次数,从而减少函数花费的时间

3。是n_zero很小还是更好?

对于通常较小的常数值,编译器非常擅长优化算法。如果编译器知道该常量,则很有可能将其完全删除。

4。按位运算在这里有帮助吗?

它肯定有作用,并且允许Clang(但据我所知,不能执行GCC)进行某些矢量化。向量化往往会更快,但并非总是如此,具体取决于您的硬件和处理的实际数据。 是否进行优化取决于n_zero的大小。考虑到您正在处理校验和,它应该很小,所以听起来像是潜在的优化。 对于已知的n_zero,使用按位运算允许编译器删除所有分支。我希望,尽管我没有测量,但速度会更快。

std::all_ofstd::string::starts_with应该完全按照您的实现进行编译,除了它们将使用&&代替&

,

在这个有趣的讨论中加上我的两分钱,尽管游戏有些迟了,但我认为您可以使用std::equal,这是一种快速方法,但方法稍有不同,使用了最大数量的硬编码字符串零,而不是零。

这可以传递给要搜索的字符串的开头和结尾的函数指针,以及指向零的字符串,特别是指向beginend的迭代器,end指向期望的零个数中的一个过去的位置,它们将由std::equal用作迭代器:

Sample

bool startsWith(const char* str,const char* end,const char* substr,const char* subend) {
    return  std::equal(str,end,substr,subend);
}
int main() {

    const char* str = "000x1234567";
    const char* substr = "0000000000000000000000000000";
    std::cout << startsWith(&str[0],&str[3],&substr[0],&substr[3]); 
}

使用@pptaszni's good answer中的测试用例和相同的测试条件:

const char* cs1 = "00000hsfhjshjshgj";
const char* cs2 = "20000hsfhjshjshgj";
const char* cs3 = "0000000000hsfhjshjshgj";
const char* cs4 = "0000100000hsfhjshjshgj";

The result where as follows

enter image description here

比使用memcmp慢,但仍然更快(除了零数少的错误结果外),并且比原始代码更一致。

,

除非n_zero很高,否则我会同意其他人的观点,即您可能会误解分析器结果。但是无论如何:

  • 是否可以将数据交换到磁盘?如果您的系统处于RAM压力之下,则在对它执行第一次操作时,数据可能会交换到磁盘上,并且需要重新加载回RAM中。 (假设此校验和检查是一段时间内对数据的首次访问。)

  • 您有可能使用多个线程/进程来利用多核处理器。

  • 也许您可以使用输入数据的统计信息/相关性,或问题的其他结构特征。

    • 例如,如果您有很多数字(例如50),并且您知道后面的数字非零的可能性更高,则可以先检查最后一位。
    • 如果几乎所有的校验和都应该匹配,则可以使用[[likely]]来向编译器提示这种情况。 (可能不会有所作为,但值得一试。)
,

使用https://docs.saltstack.com/en/latest/topics/grains/

return std::all_of(chsum_hex,chsum_hex + n_zero,[](char c){ return c == '0'; })

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 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-