首页 > 其他分享 >sizeof vs strlen - 关于代码可读性、性能考量和编译器优化

sizeof vs strlen - 关于代码可读性、性能考量和编译器优化

时间:2024-09-30 15:21:30浏览次数:3  
标签:内置 函数 代码 编译器 vs sizeof strlen 常量

1、起因

经常在咱们代码里面见到sizeof(“HEADER”)这类代码来计算常量字符串的长度,例如上次的一个代码review:

之所以这么写可能基于以下几点考虑:

(1)sizeof()是运算符而不是函数调用,编译时确定而不是运行时执行,因此不占用运行时时间

(2)strlen()是GLIBC标准库函数,运行时需要进行函数调用,因此耗时

2、分析

但是实际情况确实如咱们预想一致吗?

首先做一个性能测试,分别调用sizeof/strlen() 1000W次的对比结果,程序如下:

结果: sizeof_O0.time

sizeof_O2.time

strlen_O0.time

strlen_O2.time

从以上对比结果上可以看出两个方式对CPU的耗时基本是一致的。

很奇怪,然后继续研究一下反汇编后的代码:

汇编代码:sizeof with O0

汇编代码:strlen with O0

很明显,即使在O0情况下,编译器默认也对strlen进行了优化,这也是两者结果是基本一样的原因。

另外常量字符串长度加大并不影响该优化。

3、进一步分析

查看strlen调用的汇编可以看出,对字符串进行长度计算的时候,并没有调用bl <strlen>, 而是直接返回其长度5。

常规理解上来看,编译器在编译代码时,看到strlen,因为是标准库里的函数,会进行标准函数调用,并且去链接gcc的标准库。但是在这个例程里,编译器直接给出了字符串的长度。

本来是怀疑编译器优化级别的原因,但是即使把O2改为O0,汇编上看结果还是一样的。

3.1 原因:编译器优化

(1)首先,为什么编译后的汇编代码里会直接给出了常量字符串的长度?

GCC有一个内置的strlen函数,但在某些情况下,编译器可能会执行一种称为常量折叠的优化,尤其是在它能够确定字符串的长度在编译时是已知的情况下。

(2)为什么没有函数调用

在C语言中,有些函数是作为编译器内置的,这意味着编译器会直接将它们替换为相应的机器代码,而不是生成函数调用的代码。这通常用于性能优化,因为内置函数的调用开销更小,而且编译器可以针对具体的调用情况进行优化。

3.2 常量折叠

在编译期间,C语言的常量会被替换为其具体的值或表达式。这个过程被称为常量折叠或常量表达式计算。编译器会在编译阶段直接将常量的值替换到代码中,从而减少程序运行时的计算量和内存消耗。

常量折叠是指在编译时识别和评估常量表达式的过程,而不是在运行时计算它们。常量表达式中的术语通常是简单的字面意义,例如整数字面意义2,但它们也可能是变量,其值在编译时已经知道。考虑一下这个语句。

比如: i = 20* 20 * 10;

上面这个表达式,有2个乘号,很少有编译器的指令会对2个乘号做运算,并存到一个寄存器。所以在编译的时候i会被直接替换计算后的值4000

常量折叠可以利用算术特性。如果x是数字,即使编译器不知道x的值,0*x的值也是零。

常数折叠可能不仅仅适用于数字。字符串和常量字符串的拼接可以被常量折叠。像 "abc"+"def"这样的代码可以被替换为 "abcdef"。

总的来说,C语言的常量在编译期间的处理方式取决于常量的类型和定义方式,编译器会尽可能地优化常量的处理,以提高程序的性能和效率。

3.3 内置函数替换

(1) 编译器是怎么知道哪些函数需要替换?

编译器知道内置函数的实现并将其替换为机器代码,是因为这些函数的实现是编译器的一部分,而不是从外部库中链接进来的。这些内置函数通常包括C语言标准库中的函数(如printf、scanf、strlen等),以及编译器提供的其他常用函数(如数学函数、字符串处理函数等)。

当编译器解析源代码时,它会识别出这些内置函数的调用。编译器内部有一个预定义的内置函数列表,这些函数的实现通常位于编译器的libgcc或libstdc++库中。当编译器遇到一个内置函数调用时,它会将这个调用替换为对应的机器代码,而不是生成一个函数调用。

例如,当编译器遇到以下C代码时:

printf("Hello, World!");

它会将printf调用替换为libgcc中printf函数的机器代码,而不是生成一个函数调用。这使得调用更加快速,因为不需要跳转到外部函数的地址,也不需要执行额外的指令来执行函数调用。

如果不想编译器对内置函数进行替换,则可以使用-fno-builtin选项。-fno-builtin是GCC编译器的一个选项,用于控制编译器对内置函数的处理方式。当使用-fno-builtin选项时,是在告诉GCC不要将这些内置函数当作内置函数处理,而是按照普通的函数调用方式来编译它们。这意味着编译器会生成函数调用的代码,而不是直接替换为机器代码。

(2) 内置函数替换的时机

内置函数的替换通常发生在编译器的前端,也就是在编译器的词法分析器(lexer)、语法分析器(parser)和语义分析器(semantic analyzer)阶段。在这些阶段,编译器解析源代码,识别出函数调用,并决定是否将其替换为内置函数的实现。

内置函数的替换默认是编译器优化的一部分,但是也受到编译器配置和目标平台的影响。

(3) -O0可以取消内置函数的替换吗?

不,-O0选项不能取消内置函数的替换。-O0是GCC的优化级别选项,它表示不进行任何优化。然而,内置函数的替换是编译器默认的行为,即使在-O0优化级别下,内置函数仍然会被替换为机器代码。内置函数的替换是编译器实现的一部分,旨在提供高性能的调用,而不需要额外的函数调用开销。这种优化是编译器自动进行的,不受优化级别的影响。

(4) 关于strlen()函数的进一步分析

即使对于正常的动态字符串的函数调用,GNU提供的C标准库中strlen的实现实际上已经针对性能做了一些优化。比如每次测试四个字节来代替传统实现中每次测试一个字节的方法,以及通过跟预设值的位与操作来进行快速比较。具体可以参考glibc中的实现。

此外在一些平台上,strlen也会使用SIMD等指令集或并行处理来加速遍历过程。

另外即使在性能关键路径,比如在循环处理过程中,编译器也会根据代码上下文判断出strlen()的参数字符串在该自治代码块中是否恒定,来决定是否对其进行编码优化,如果满足条件,编译器会毫不犹豫的优化代码。

4、结论

  • 对于常量字符串来说,使用strlen并不会比sizeof降低性能,因为编译器对常量字符串进行了编译优化,且对内置函数进行了替换。

  • 代码可读性是我们写代码时要考虑的重要因素,因此在计算常量字符串长度时,strlen要比sizeof更好。

  • 性能对于非关键节点代码来说,并不是首要考虑因素。

  • 编译器默认已经做了很多优化。

注:

本例不考虑sizeof在C99中对动态数组的支持

标签:内置,函数,代码,编译器,vs,sizeof,strlen,常量
From: https://blog.csdn.net/bitwzy001/article/details/142657351

相关文章

  • 应用层需要 AI 编译器
    应用层需要AI编译器从硬件角度以及底层软件的角度来看待为什么需要AI编译器的,而现在可以换一个上层应用视角来看待这个问题。以深度神经网络为技术基础的人工智能领域在近些年发展十分迅速,从10年前,AI技术可能只能解决图形分类等较为简单的任务,但如今无论是推荐系统、大语......
  • 为什么需要 AI 编译器
    为什么需要AI编译器随着硬件技术的不断进步,进入了一个新的计算加速时代,这个时代的硬件平台变得越来越复杂和多层次。现代计算加速平台采用了多层架构,包括标量、向量、多核、多包、多机架等不同层次的并行处理能力。这种设计不仅提高了性能,也增加了硬件设计的复杂性。同时,现代计......
  • 传统编译器与 AI 编译器区别
    传统编译器与AI编译器区别接下来,来了解一下AI编译器与传统编译器的区别与联系。1.区别与联系1)目标相同:AI编译器与传统编译器都是通过自动化的方式进行程序优化和代码生成,从而节省大量的人力对不同底层硬件的手动优化。2)优化方式类似:在编译优化层,AI编译器与传统编译器都......
  • 全网最详细Open vSwitch 应用实践
    目录一、  实验环境二、  技术与知识背景1.OpenvSwitch概述2.OVS各模块的简要介绍三.、基于OpenvSwitch的OpenFlow实践1.OVS常用操作四、  基础技能1.    内容与任务2.    规划与准备五、  基础技能的配置与操作要点1.  ......
  • VM--VSphere 7安装部署 (详细教程 EXSI+VCenter)
    目录一、Vsphere7简介二、Vsphere7.0安装部署    2.1服务器阵列卡配置安装  2.1.1服务器配置磁盘阵列2.1.2创建磁盘阵列2.1.3选取硬盘2.1.4选择RAID方式2.2VMwareESXi系统安装2.2.1 选择启动项2.2.2进入系统安装2.2.3确认安装2.2.4接受许可2.2.5......
  • 实战 vSphere 7 vMotion 迁移说明 ( Vcenetr 内迁移、 跨vcenter 迁移)
    目录一、vMotion简介二、Vcenter内虚拟机迁移2.1无共享存储的环境中的vMotion具有以下要求和限制2.2网卡启用vMotion​编辑2.3右键单击虚拟机,然后选择迁移2.4选择更改计算资源和存储2.5选择虚拟机的目标资源2.6选择虚拟机磁盘格式2.7选择虚拟机存储策略2.......
  • 法定每月计薪天数 vs 法定每月工作天数 All In One
    法定每月计薪天数vs法定每月工作天数AllInOne法定每月记薪天数21.75天/月,用于计算工资的发放,缺勤、事假的工资扣除(365-104-11)/12//20.833333333333332(365-115)/12//20.833333333333332法定每月工作天数20.83天/月,用于计算加班时长、加班费......
  • vscode分布式调试
    CUDA_VISIBLE_DEVICES=1tools/dist_test.shprojects/configs/StreamPETR/stream_petr_r50_flash_704_bs2_seq_90e.py/home2/workspace/zxy/StreamPETR-main/work_dirs/stream_petr_r50_flash_704_bs2_seq_90e/latest.pth1--evalbboxsh文件#!/usr/bin/envbashCONFIG=......
  • VS控制台出现debug()乱码问题
    出现的问题之前项目debug()控制台打印正常,增删后发现只有debug()将强转到QString::fromLocal8Bit才可以显示出来,VS控制台或调试器默认使用UTF-8解码,我通过cmd控制台查看自身活动代码为936(简体中文的GBK编码)因此不兼容有两种方法,我用的是第二种,建议第二种方法一:在控制台中......
  • vscode监听代码变动重启node
    在VisualStudioCode(VSCode)中,可以使用nodemon来监听代码的变动并自动重启Node.js服务器。nodemon是一个工具,它可以监控文件的变化并在检测到变化时自动重启Node.js应用程序。安装nodemon你需要在项目中安装nodemon,可以使用npm全局安装或者将其作为开发依赖安装......