这似乎是一个愚蠢的问题,但是return xxx;
在一个明确定义的函数中"执行" 的确切时刻?
请参阅以下示例以了解我的意思(现在直播):
#include <iostream>
#include <string>
#include <utility>
//changes the value of the underlying buffer
//when destructed
class Writer{
public:
std::string &s;
Writer(std::string &s_):s(s_){}
~Writer(){
s+="B";
}
};
std::string make_string_ok(){
std::string res("A");
Writer w(res);
return res;
}
int main() {
std::cout<<make_string_ok()<<std::endl;
}
Run Code Online (Sandbox Code Playgroud)
我天真地期待发生的事情make_string_ok
被称为:
res
被调用(价值res
就是"A"
)w
调用构造函数return res
被执行.应该返回res的当前值(通过复制当前值res
),即"A"
.w
被称为析构函数,值res
变为"AB"
.res
函数被称为.所以我希望"A"
结果,但"AB" …
我一直认为,临时对象一直存在,直到完整表达结束.然而,这是a std::vector
和数组的初始化之间的奇怪差异.
请考虑以下代码:
#include <iostream>
#include <vector>
struct ID{
static int cnt;
// the number of living object of class ID at the moment of creation:
int id;
ID():id(++cnt){}
~ID(){
cnt--;
}
};
int ID::cnt=0;
int main(){
int arr[]{ID().id, ID().id};
std::vector<int> vec{ID().id, ID().id};
std::cout<<" Array: "<<arr[0]<<", "<<arr[1]<<"\n";
std::cout<<" Vector: "<<vec[0]<<", "<<vec[1]<<"\n";
}
Run Code Online (Sandbox Code Playgroud)
这个程序的输出有点意外(至少对我而言)意外:
Array: 1, 1
Vector: 1, 2
Run Code Online (Sandbox Code Playgroud)
这意味着,临时对象在整个初始化期间是活动的,std::vector
但是在阵列的情况下它们被一个接一个地创建和销毁.我希望临时工可以活到完全表达int arr[]{ID().id, ID().id};
完成之前.
该标准提到了一个关于临时对象的生命周期和数组初始化的例外(12.2).但是,我没有得到它的含义,也不知道为什么它适用于这种特殊情况:
有两种情况,临时表在与完整表达结束时不同的地方被摧毁.第一个上下文是调用默认构造函数来初始化数组的元素.如果构造函数具有一个或多个默认参数,则在构造下一个数组元素(如果有)之前,对默认参数中创建的每个临时的销毁进行排序.
不同编译器的结果概述(MSVS结果是NathanOliver的简称):
Array Vector
clang 3.8 1, 2 1, 2
g++ 6.1 …
Run Code Online (Sandbox Code Playgroud) 这个答案 @Dunes的状态,即由于管道-ING有(几乎)没有浮点乘法和除法之间的差别.但是,从我对其他语言的看法来看,我认为这种划分会更慢.
我的小测试看起来如下:
A=np.random.rand(size)
command(A)
Run Code Online (Sandbox Code Playgroud)
对于不同的命令,size=1e8
我在我的机器上得到以下时间:
Command: Time[in sec]:
A/=0.5 2.88435101509
A/=0.51 5.22591209412
A*=2.0 1.1831600666
A*2.0 3.44263911247 //not in-place, more cache misses?
A+=A 1.2827270031
Run Code Online (Sandbox Code Playgroud)
最有趣的部分:除以0.5
几乎是除以的两倍0.51
.可以假设,这是由于一些智能优化,例如替换除法A+A
.不过的时机A*2
和A+A
太过离谱支持这一要求.
一般来说,使用浮点数除以值(1/2)^n
的速度更快:
Size: 1e8
Command: Time[in sec]:
A/=0.5 2.85750007629
A/=0.25 2.91607499123
A/=0.125 2.89376401901
A/=2.0 2.84901714325
A/=4.0 2.84493684769
A/=3.0 5.00480890274
A/=0.75 5.0354950428
A/=0.51 5.05687212944
Run Code Online (Sandbox Code Playgroud)
如果我们看一下,它会变得更有趣size=1e4
:
Command: 1e4*Time[in sec]:
A/=0.5 3.37723994255
A/=0.51 3.42854404449
A*=2.0 1.1587908268
A*2.0 1.19793796539
A+=A 1.11329007149
Run Code Online (Sandbox Code Playgroud)
现在,有一个由部门之间没有区别.5 …
在一个自学项目中,我借助以下代码测量内存的带宽(这里解释,整个代码在问题的最后跟随):
unsigned int doit(const std::vector<unsigned int> &mem){
const size_t BLOCK_SIZE=16;
size_t n = mem.size();
unsigned int result=0;
for(size_t i=0;i<n;i+=BLOCK_SIZE){
result+=mem[i];
}
return result;
}
//... initialize mem, result and so on
int NITER = 200;
//... measure time of
for(int i=0;i<NITER;i++)
resul+=doit(mem)
Run Code Online (Sandbox Code Playgroud)
BLOCK_SIZE
以这种方式选择,每个整数加法获取整个64字节高速缓存行.我的机器(Intel-Broadwell)每个整数附加需要大约0.35纳秒,所以上面的代码可以使带宽饱和到高达182GB/s(这个值只是一个上限,可能很大,很重要的是不同尺寸的带宽比率).代码用g++
和编译-O3
.
改变向量的大小,我可以观察到L1(*) - ,L2-,L3-高速缓存和RAM-内存的预期带宽:
但是,我真的很难解释一下:L1高速缓存测量带宽的崩溃大小约为2 kB,这里的分辨率要高一些:
我可以在我有权访问的所有机器上重现结果(具有Intel-Broadwell和Intel-Haswell处理器).
我的问题:内存大小大约2 KB的性能崩溃是什么原因?
(*)我希望我理解正确,对于L1缓存而言,不是64字节,而是每次添加只有4个字节被读取/传输(没有更快的缓存,其中必须填充缓存行),因此L1的绘制带宽是只有上限而不是badwidth本身.
编辑:当选择内部for循环中的步长时
即当内环由约31-35步/读时组成时.这意味着崩溃不是由于内存大小,而是由于内循环中的步数.
可以通过分支未命中来解释,如@ user10605163的好答案所示.
列出重现结果
bandwidth.cpp
:
#include <vector>
#include <chrono>
#include <iostream>
#include <algorithm>
//returns minimal …
Run Code Online (Sandbox Code Playgroud) 当编译为C程序或C++程序(对于Linux x86-64)时,小程序的程序集之间存在奇怪的差异.
有问题的代码:
int fun();
int main(){
return fun();
}
Run Code Online (Sandbox Code Playgroud)
将其编译为C程序(带gcc -O2
)产生:
main:
xorl %eax, %eax
jmp fun
Run Code Online (Sandbox Code Playgroud)
但将其编译为C++ - 程序(带g++ -02
)产生:
main:
jmp _Z3funv
Run Code Online (Sandbox Code Playgroud)
我觉得很困惑,C版本用0
(xorl %eax, %eax
)初始化主函数的返回值.
C语言的哪个特性对这种必要性负责?
编辑:确实,因为int fun(void);
没有初始化eax寄存器.
如果根本没有原型fun
,即:
int main(){
return fun();
}
Run Code Online (Sandbox Code Playgroud)
然后C编译器再次将eax寄存器归零.
在分析我的算法的内存消耗时,我很惊讶有时对于较小的输入需要更多的内存.
这一切都归结为以下用法pandas.unique()
:
import numpy as np
import pandas as pd
import sys
N=int(sys.argv[1])
a=np.arange(N, dtype=np.int64)
b=pd.unique(a)
Run Code Online (Sandbox Code Playgroud)
有N=6*10^7
需要3.7GB
的峰值内存,但与N=8*10^7
"唯一" 3GB
.
扫描不同的输入大小会产生以下图表:
出于好奇和自我教育:如何反直觉的行为(即较小的输入大小更多的内存)左右N=5*10^7
,N=1.3*10^7
来解释?
以下是在Linux上生成内存消耗图的脚本:
pandas_unique_test.py:
import numpy as np
import pandas as pd
import sys
N=int(sys.argv[1])
a=np.arange(N, dtype=np.int64)
b=pd.unique(a)
Run Code Online (Sandbox Code Playgroud)
show_memory.py:
import sys
import matplotlib.pyplot as plt
ns=[]
mems=[]
for line in sys.stdin.readlines():
n,mem = map(int, line.strip().split(" "))
ns.append(n)
mems.append(mem)
plt.plot(ns, mems, label='peak-memory')
plt.xlabel('n')
plt.ylabel('peak memory in …
Run Code Online (Sandbox Code Playgroud) 我尝试了解System V AMD64 的含义-ABI的调用约定并查看以下示例:
struct Vec3{
double x, y, z;
};
struct Vec3 do_something(void);
void use(struct Vec3 * out){
*out = do_something();
}
Run Code Online (Sandbox Code Playgroud)
甲Vec3
-variable是型存储器的,因此调用者(use
)必须返回的变量分配空间并把它传递为隐藏指针到被叫方(即,do_something
)。这是我们在生成的汇编器中看到的(在godbolt上,使用编译-O2
):
use:
pushq %rbx
movq %rdi, %rbx ;remember out
subq $32, %rsp ;memory for returned object
movq %rsp, %rdi ;hidden pointer to %rdi
call do_something
movdqu (%rsp), %xmm0 ;copy memory to out
movq 16(%rsp), %rax
movups %xmm0, (%rbx)
movq %rax, 16(%rbx)
addq $32, …
Run Code Online (Sandbox Code Playgroud) 我有一个纯Python脚本,我想分发给具有未知Python配置的系统.因此,我想将Python代码编译为独立的可执行文件.
我cython --embed ./foo.py
没有问题就跑了foo.c
.然后,我跑了
gcc $(python3-config --cflags) $(python3-config --ldflags) ./foo.c
Run Code Online (Sandbox Code Playgroud)
哪里python3-config --cflags
给
-I/usr/include/python3.5m -I/usr/include/python3.5m -Wno-unused-result -Wsign-compare -g -fdebug-prefix-map=/build/python3.5-MLq5fN/python3.5-3.5.3=. -fstack-protector-strong -Wformat -Werror=format-security -DNDEBUG -g -fwrapv -O3 -Wall -Wstrict-prototypes
Run Code Online (Sandbox Code Playgroud)
并python3-config --ldflags
给出
-L/usr/lib/python3.5/config-3.5m-x86_64-linux-gnu -L/usr/lib -lpython3.5m -lpthread -ldl -lutil -lm -Xlinker -export-dynamic -Wl,-O1 -Wl,-Bsymbolic-functions
Run Code Online (Sandbox Code Playgroud)
这样我就可以获得一个没有问题的动态链接可执行文件.ldd a.out
产量
linux-vdso.so.1 (0x00007ffcd57fd000)
libpython3.5m.so.1.0 => /usr/lib/x86_64-linux-gnu/libpython3.5m.so.1.0 (0x00007fda76823000)
libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007fda76603000)
libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007fda763fb000)
libutil.so.1 => /lib/x86_64-linux-gnu/libutil.so.1 (0x00007fda761f3000)
libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007fda75eeb000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fda75b4b000)
libexpat.so.1 => …
Run Code Online (Sandbox Code Playgroud) 我试图了解System V AMD64-ABI对于从函数按值返回的含义。
对于以下数据类型
struct Vec3{
double x, y, z;
};
Run Code Online (Sandbox Code Playgroud)
该类型Vec3
属于MEMORY类,因此ABI就“返回值”指定了以下内容:
如果类型具有MEMORY类,则调用方将为返回值提供空间,并以%rdi形式传递此存储的地址,就好像它是该函数的第一个参数一样。实际上,此地址成为“隐藏”的第一个参数。该存储不得与通过此参数以外的其他名称对被调用方可见的任何数据重叠。
返回时,%rax将包含调用者在%rdi中传递的地址。
考虑到这一点,以下(傻)功能:
struct Vec3 create(void);
struct Vec3 use(){
return create();
}
Run Code Online (Sandbox Code Playgroud)
可以编译为:
use_v2:
jmp create
Run Code Online (Sandbox Code Playgroud)
我认为,正如ABI所保证的,可以执行尾调用优化,这create
会将%rdi
传递的值放入%rax
寄存器中。
但是,似乎没有一个编译器(gcc,clang和icc)正在执行此优化(此处为godbolt)。生成的汇编代码%rdi
仅能将其值移动到%rax
,因此保存在堆栈上,例如gcc:
use:
pushq %r12
movq %rdi, %r12
call create
movq %r12, %rax
popq %r12
ret
Run Code Online (Sandbox Code Playgroud)
无论是对于这种最小的,愚蠢的功能,还是对于现实生活中更复杂的功能,都不会执行尾调用优化。这使我相信,我必须丢失某些东西,这是禁止的。
毋庸置疑,对于SSE类的类型(例如,仅2而不是3的两倍),将执行尾调用优化(至少由gcc和clang进行,仅靠戈德螺栓)
use_v2:
jmp create
Run Code Online (Sandbox Code Playgroud)
结果是
use:
jmp create
Run Code Online (Sandbox Code Playgroud) 这个setup.py:
from distutils.core import setup
from distutils.extension import Extension
from Cython.Build import cythonize
extensions = (
Extension('myext', ['myext/__init__.py',
'myext/algorithms/__init__.py',
'myext/algorithms/dumb.py',
'myext/algorithms/combine.py'])
)
setup(
name='myext',
ext_modules=cythonize(extensions)
)
Run Code Online (Sandbox Code Playgroud)
没有预期的效果.我想要它生产单一的myext.so
,它做的; 但是当我通过它调用它时
python -m myext.so
Run Code Online (Sandbox Code Playgroud)
我明白了:
ValueError: Attempted relative import in non-package
Run Code Online (Sandbox Code Playgroud)
由于myext
试图参考的事实.algorithms
.
知道如何让这个工作吗?