c++ - 如何考虑 C++ 中的各种 for 循环?
问题描述
我的 IDE (CLion) 建议用 foreach 替换 for 循环,其中循环的元素是 char 类型值的地址(选项 2)。我很好奇以下几点:
表达选项 2 中发生的事情的最佳方式是什么?我们是否遍历明文中每个字符的内存位置?
选项 2 和 3 有何不同?
选项 3 是否在每次迭代时为新字符分配内存?
选项1
void Cipher(std::string &plaintext, int key) {
for (int i = 0; i < plaintext.length(); i++) {...}
}
选项 2
void Cipher(std::string &plaintext, int key) {
for (char &letter : plaintext) {...}
}
选项 3
void Cipher(std::string &plaintext, int key) {
for (char letter : plaintext) {...}
}
解决方案
应该不惜一切代价避免选项1!!!这里的问题是方法的输入(明文)是一个引用,因此字符串存在于方法的范围之外。这意味着编译器无法确定该变量的范围,因此无法确定执行优化是否安全(并非总是如此,但在这里)。
在这里实现一个愚蠢的方法(只需将 12 添加到每个字符)。您会注意到第一个版本的 ASM 看起来“不错”。它非常简单,非常小,很棒。但是,如果您将 1 切换为 0 并与第二种方法进行比较,您会注意到第二种方法在生成的 asm 数量方面呈爆炸式增长,但是当您仔细观察时,它并没有那么糟糕。
看一下第一个代码片段,我们可以在内循环的第一行中看到:
mov rcx, qword ptr [rdi]
这有点糟糕。它实际上是在每次迭代时读取字符串“开始”指针(假设另一个线程*可能*调整字符串大小,因此更改字符串长度)。
但是,如果您查看第二种方法,它会使用 vpaddb 指令(使用 YMM 寄存器)生成一些展开的循环。这意味着它一次处理 32 个字符(与第一种方法不同,它一次只能处理 1 个字符)。
如果你想开始让 option1 接近 option2 的性能,你需要做一些严峻的事情,比如:
void Cipher(std::string &plaintext, int key) {
if(!plaintext.empty())
{
char* ptr = &plaintext[0];
for (int i = 0, length = plaintext.length(); i < length; i++) {
ptr[i] += 12;
}
}
}
现在这个可怕的变化意味着编译器可以看到 ptr 和 length 变量在函数范围内没有变化,因此它现在能够向量化代码。(不过,选项 2 和 3 仍然更有效!)
Option3 不会在每次迭代时分配一个字符(它会将一个字符加载到通用寄存器中,或者将一组字符加载到 YMM 寄存器中)。在这种情况下,性能差异是没有实际意义的。如果要修改字符串,请使用 option2,如果字符串是只读的,请使用 option3。
实现相同目的的一个较旧的替代方案是 std::for_each,但是它不再比基于范围的 for 循环更可取。
推荐阅读
- c# - ASP.NET Core 中的位置记录属性
- bitbucket - 来自 bitbucket 云的 TeamCity 拉取请求
- php - mysql 用户正常,但使用 laravel db:seed 时失败
- python - Python:在导入全部(*)中使用字符串变量
- powershell - 创建静态 IP 工具
- python - 意外的参数传递字典以使用 **kwargs python3.9 运行
- javascript - Firestore 用户 ID 与文档 ID 不同,反应原生 Firebase
- django - 如何在git中推送单个文件
- ionic-framework - 显示相对于元素的 Ionic 弹出框而不单击
- html - 预加载顺序重要吗?