许多样式指南(例如Google建议使用int索引数组时作为默认整数使用).随着64位平台的兴起,大多数时候a int只有32位,这不是平台的自然宽度.因此,除了简单的说法,我认为没有理由保持这种选择.我们清楚地看到编译以下代码的位置:
double get(const double* p, int k) {
return p[k];
}
Run Code Online (Sandbox Code Playgroud)
被编译成
movslq %esi, %rsi
vmovsd (%rdi,%rsi,8), %xmm0
ret
Run Code Online (Sandbox Code Playgroud)
其中第一条指令将32位整数提升为64位整数.
如果代码转换成
double get(const double* p, std::ptrdiff_t k) {
return p[k];
}
Run Code Online (Sandbox Code Playgroud)
生成的程序集现在
vmovsd (%rdi,%rsi,8), %xmm0
ret
Run Code Online (Sandbox Code Playgroud)
这清楚地表明,CPU感觉更在家std::ptrdiff_t比使用int.许多C++用户已经迁移到了std::size_t,但我不想使用无符号整数,除非我真的需要模数2^n行为.
在大多数情况下,使用int不会损害性能,因为未定义的行为或有符号整数溢出允许编译器在内部将任何内容int提升为std::ptrdiff_t处理索引的in循环,但我们从上面清楚地看到编译器不会感到宾至如归int.此外,std::ptrdiff_t在64位平台上使用会使溢出不太可能发生,因为当我们看到越来越多的人被int溢出困住时,他们必须处理大于2^31 - 1现在变得非常普遍的整数.
从我所看到的,这使得唯一int脱颖而出似乎是文字,如事实5是int,但我不认为它会引起任何问题,如果我们移动到std::ptrdiff_t一个默认的整数.
我即将成为std::ptrdiff_t我小公司编写的所有代码的事实上的标准整数.这有什么理由可能是一个糟糕的选择吗?
PS:我同意这个名字 …
我在Windows和Linux(x86-64)上运行程序.它使用相同的编译器(Intel Parallel Studio XE 2017)编译,具有相同的选项,Windows版本比Linux版本快3倍.罪魁祸首是对std :: erf的调用,在两种情况下都在英特尔数学库中解析(默认情况下,它在Windows上动态链接,在Linux上静态链接,但在Linux上使用动态链接可以提供相同的性能).
这是一个重现问题的简单程序.
#include <cmath>
#include <cstdio>
int main() {
int n = 100000000;
float sum = 1.0f;
for (int k = 0; k < n; k++) {
sum += std::erf(sum);
}
std::printf("%7.2f\n", sum);
}
Run Code Online (Sandbox Code Playgroud)
当我使用vTune分析这个程序时,我发现Windows和Linux版本之间的程序集有点不同.这是Windows上的调用站点(循环)
Block 3:
"vmovaps xmm0, xmm6"
call 0x1400023e0 <erff>
Block 4:
inc ebx
"vaddss xmm6, xmm6, xmm0"
"cmp ebx, 0x5f5e100"
jl 0x14000103f <Block 3>
Run Code Online (Sandbox Code Playgroud)
并在Windows上调用erf函数的开头
Block 1:
push rbp
"sub rsp, 0x40"
"lea rbp, ptr [rsp+0x20]"
"lea rcx, …Run Code Online (Sandbox Code Playgroud) 在他的一个主题演讲中,Andrei Alexandrescu建议,在64位平台上,使用32位数组索引比使用原始指针更快:
第16页:http://www.slideshare.net/andreialexandrescu1/three-optimization-tips-for-c-15708507
在他的Facebook帐户上,他更精确,并说:"更喜欢数组索引到指针(这个似乎每十年逆转一次)."
我已经尝试了许多方法来找到差异,但我还没有设法构建任何显示这种差异的程序.知道安德烈,我不会感到惊讶,差异不超过百分之几,但如果有人找到这样的例子,我会很高兴.
这是我做过的测试.我选择n = 5000,足够大以获得合适的时序并且足够小以使一切都适合L1缓存.我循环了几次,以便CPU频率上升.
#include <iostream>
#include <chrono>
int main(int argc, const char* argv[]) {
const int n{5000};
int* p{new int[n]};
// Warm up the cache
for (int i{0}; i < n; i++) {
p[i] += 1;
}
for (int j{0}; j < 5; j++) {
{
auto start_pointer = std::chrono::high_resolution_clock::now();
for (int* q{p}; q != p + n; ++q) {
++(*q);
}
auto end_pointer = std::chrono::high_resolution_clock::now();
auto time_pointer = std::chrono::duration_cast<std::chrono::nanoseconds>(
end_pointer - …Run Code Online (Sandbox Code Playgroud) 如果你有一个方法,并且你想给编译器一个暗示它是一个好主意内联它,你目前有两个解决方案.第一个是在声明类时定义方法:
class Vector {
private:
double* data_;
double* size_;
double* capacity_;
public:
double& operator[](int k) {
return data_[k];
}
...
}
Run Code Online (Sandbox Code Playgroud)
由于此方法可能会降低可读性,另一种解决方案是使用inline关键字并在类外定义方法:
class Vector {
private:
double* data_;
double* size_;
double* capacity_;
public:
inline double& operator[](int k);
...
}
double& Vector::operator[](int k) {
return data_[k];
}
Run Code Online (Sandbox Code Playgroud)
这使得代码更具可读性(至少我更喜欢它).阅读我的STL实现,我发现他们使用了两者的混合.一些方法(我认为应该真正内联的方法)在类中定义,而其他方法则使用inline关键字在类外定义.该文件也以该类的注释声明开头.
所以我的问题如下.当前的编译器(我正在考虑GCC,Clang,Intel和Visual Studio)更有可能内联在类中声明的成员函数,而不是使用inline关键字在类外声明的成员函数?
备注:这个问题与我何时应该为函数/方法编写关键字'inline'不重复?我的问题是关于编译器实现.这两种方式表明你希望这些函数被内联是等价的.STL的编写方式表明它们不是.
我std::vector< char >::max_size()对n = 32和n = 64位系统的测试结果感到困惑.结果是2 n - 1.让我解释为什么我感到困惑.
每执行std::vector<T>,我知道有一个类型的三个成员T*:begin_,end_,capacity_.
begin_指向向量的第一个值并end_指向最后一个向量的值.因此,矢量的大小由下式给出end_ - begin_.但是这种差异的结果是在我知道的每个实现上都是n位std::ptrdiff_t有符号整数的类型.
因此,这种类型不能存储2 ñ - 1,但仅达2 ñ - 1 - 1.如果你看看你std::vector实现,你会清楚地看到,尺寸使得它转换成一无符号之前的2个指针(差别整数).
那么,为什么他们可以假装存储超过2 n -1个元素而不会破坏.size()?
我刚刚发现以下代码与gcc 5.4和Intel编译器18.0.2一起编译.Clang 6.0.0只是发出警告.
#include <vector>
int main() {
std::vector<double> v = v;
return 0;
}
Run Code Online (Sandbox Code Playgroud)
我的代码中有一个非常相似的错误,我担心这些代码可以编译.我的问题是:
我非常关注代码的性能和可读性,我从Google的Chandler Carruth那里得到了我的大部分想法.我想在不丢失性能的情况下为C++应用以下规则来实现干净的代码.
这样,功能没有副作用.这是代码可读性的必要条件,并使C++成为一种功能.现在来了表现.如果你想编写一个为std :: vector的每个元素添加1的函数,你能做什么?这是我的解决方案.
std::vector<int> add_one(std::vector<int> v) {
for (std::size_t k = 0; k < v.size(); ++k) {
v[k] += 1;
}
return v;
}
...
v = add_one(std::move(v));
...
Run Code Online (Sandbox Code Playgroud)
我发现这非常优雅,只有2个动作.这是我的问题:
PS:人们问我为什么不喜欢通过参考.我有两个参数:
1 - 它没有在调用站点明确指出哪个参数可能会发生变异.
2 - 它有时会扼杀性能.由于别名,引用和指针是编译器的地狱.我们来看下面的代码
std::array<double, 2> fval(const std::array<double, 2>& v) {
std::array<double, 2> ans;
ans[0] = cos(v[0] + v[1]);
ans[1] = sin(v[0] + v[1]);
return ans;
}
Run Code Online (Sandbox Code Playgroud)
将ans作为参考的相同代码慢了2倍:
std::array<double, 2> fref(const std::array<double, 2>& v,
std::array<double, …Run Code Online (Sandbox Code Playgroud) 我一直在使用std :: vector来理解何时构造,破坏,复制构造和移动构造的对象.为此,我编写了以下程序
#include <iostream>
#include <vector>
class Test {
public:
Test() {
std::cout << "Constructor called for " << this << std::endl;
}
Test(const Test& x) {
std::cout << "Copy Constructor called for " << this << std::endl;
}
Test(Test&& x) {
std::cout << "Move Constructor called for " << this << std::endl;
}
~Test() {
std::cout << "Destructor called for " << this << std::endl;
}
};
int main() {
std::vector<Test> a( 1 );
a.resize(3);
return 0;
} …Run Code Online (Sandbox Code Playgroud) 在他的演讲"效率与算法,性能与数据结构"中,Chandler Carruth谈到了在C++中需要更好的分配器模型.当前的分配器模型侵入了类型系统,因此很难在许多项目中工作.另一方面,Bloomberg分配器模型不会入侵类型系统,而是基于虚函数调用,这使得编译器无法"看到"分配并对其进行优化.在他的演讲中,他谈到编译器重复删除内存分配(1:06:47).
我花了一些时间来找到一些内存分配优化的例子,但我发现这个代码示例,在clang下编译,优化掉所有的内存分配,只返回1000000而不分配任何东西.
template<typename T>
T* create() { return new T(); }
int main() {
auto result = 0;
for (auto i = 0; i < 1000000; ++i) {
result += (create<int>() != nullptr);
}
return result;
}
Run Code Online (Sandbox Code Playgroud)
以下论文http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3664.html也说分配可以在编译器中融合,似乎表明一些编译器已经做了那种排序东西的.
由于我对高效内存分配的策略非常感兴趣,我真的很想理解为什么Chandler Carruth反对Bloomberg模型中的虚拟调用.上面的例子清楚地表明,clang可以在看到分配时优化.
Google不会在其C++代码库中使用异常.对于错误,它们使用一个名为status的类,而不是程序员从函数返回时必须检查的类.否则程序无法编译(在41:34 链接https://www.youtube.com/watch?v=NOCElcMcFik).我有几个问题:
1)网上是否有免费提供该课程的例子?
2)对于"void f()",可以使用可以转换成"状态f()"的副作用.但是如果你的函数已经返回一个值怎么办?Google不允许传递非const的引用,因此您无法改变提供给您的Status对象.那他们怎么办?
谢谢你的帮助.
c++ ×10
c++11 ×2
move ×2
vector ×2
allocation ×1
arrays ×1
assembly ×1
exception ×1
icc ×1
inline ×1
intel-vtune ×1
memory ×1
optimization ×1
performance ×1
pointers ×1
x86-64 ×1