从reddit/hackernews/lobsters/摘抄一些c++动态
这周周末有事,发的比较早
周刊项目地址|在线地址 |知乎专栏 | 腾讯云+社区 |
欢迎投稿,推荐或自荐文章/软件/资源等,请提交 issue
大概内容,rust并没有比c++快和安全。唯一优点就是生命周期检查
很多代码场景下c++的灵活性要高于强制安全检查,且一些场景下rust生成的汇编不如c++少
serenity是一个c++写的操作系统,分享了一些开发记录/采访
一篇CRTP示例。主要解决的问题,基本接口实现,不需要virtual
TODO:看不懂讲的啥
用c写个lisp
讲各种各样的cast
这里着重介绍一下bit_cast,这个就是强制解释的memcpy版本,对于内建基础类型使用的,比如
#include <cstdint>
#include <bit>
#include <iostream>
constexpr double f64v = 19880124.0;
constexpr auto u64v = std::bit_cast<std::uint64_t>(f64v);
constexpr std::uint64_t u64v2 = 0x3fe9000000000000ull;
constexpr auto f64v2 = std::bit_cast<double>(u64v2);
int main()
{
std::cout
<< std::fixed <<f64v << "f64.to_bits() == 0x"
<< std::hex << u64v << "u64\n";
std::cout
<< "f64::from_bits(0x" << std::hex << u64v2 << "u64) == "
<< std::fixed << f64v2 << "f64\n";
}
实现就是memcpy硬拷,其实这种需求用union不就搞定了。多个copy换安全吗
考虑benchmark一段代码
bench_input = 42;
start_time = time();
bench_output = run_bench(bench_input);
result = time() - start_time;
这段代码的问题在于,编译器可能会重排time()导致run_bench的时间不准确
要保证,run_bench必须在两条time计算之间,不会被优化/重排 如何做?
google benchmark已经做过类似的工作DoNotOptimize()
bench_input = 42;
start_time = time();
DoNotOptimize(bench_input)
bench_output = run_bench(bench_input);
DoNotOptimize(bench_output)
result = time() - start_time;
DoNotOptimize()
的作用是如何实现的?
inline BENCHMARK_ALWAYS_INLINE void DoNotOptimize(Tp& value) {
asm volatile("" : "+r,m"(value) : : "memory");
}
这个asm可能看不懂,根据GNU extended inline asm syntax,是这个意思
asm asm-qualifiers ( AssemblerTemplate
: OutputOperands
[ : InputOperands
[ : Clobbers ] ])
针对这行asm,volatile暗示会变,让编译器不优化,AssemblerTemplate是空的,也就是明显是空的无作用的汇编也不要删掉?
“memory” 也就是 “clobbers memory” 明示直接内存读,也就是暗示这个值经常变
output constraints ("+r,m"(value)
明示读写这个value
是不是必须要用clobber memory?
有个类似的实现
inline BENCHMARK_ALWAYS_INLINE void EnsureMaterialise(Tp& value) {
asm volatile("" : "+r,m"(value) : :); // Doesn't clobber memory.
}
也是暗示value会变,也是暗示value不被优化,但是不能保证value的全局副作用,还是会被重排,这个用来测试比如jit优化constant propagation优化之类的场景,看差异
我们要保证,value的计算是影响周围的调用的,所以,要标记value是可变只能从内存读/寄存器读读(clobber memory)这样就有全局副作用,对于相关的函数调用,能保证不被重排。
所以重新回顾一下上面这段代码
bench_input = 42;
// May have global side-effects.
start_time = time();
// Also may have global side-effects.
// Needs to observe any side-effects of `time()`, so can't be re-ordered before it.
// Forces `bench_input` to be materialized, despite it being a constant.
DoNotOptimize(bench_input)
// Here the compiler must assume that `bench_input` has now been mutated.
// Is expected to observe the potentially mutated value of `bench_input`, therefore
// cannot be reordered before `DoNotOptimize()`.
bench_output = run_bench(bench_input);
// May have global side-effects.
// Depends on `bench_output` so cannot be reordered above `run_bench()`.
DoNotOptimize(bench_output)
// Also may have global side-effects.
// Needs to observe any potential side effects of `DoNotOptimize(bench_output)`, so
// cannot be reordered before it.
result = time() - start_time;
有一些不是英语还没有字幕,实在看不懂,跳过
Video Rendering on Frontend and Backend介绍视频rendering相关技术栈 ffmpeg opengl之类的 相当于业内知识分享。有做这方面的可以看看
介绍了协程的几个猥琐用法
比如用于树的遍历,协程的栈比函数栈要省
Clang/gcc 用-Rpass可以看到优化的具体细节,不方便看?想要其他细节?借助工具,opt-viewer就是这么个工具,llvm组件里带的
但是有一定的缺点,CPU占用/内存之类的,作者改了一个optview2
并展示了一些用法示例,这个工具对于编译器分析有点帮助。
介绍了参数传递对性能的影响,列了一些极端场景。这里贴例子
传值比传引用重?传引用比传值轻?一般来说是,也有反例
STL中的场景
拷贝不可避免,比如accumulate,也更安全,比如transform
下面是几个好玩的例子(坑爹的用法)
const T&不一定是不可变的
void scale_down(vector<double>& v, const double& a) {
for(auto&i : v) i /= a;
}
std::vector<double> a1{2, 2, 2};
scale_douw(a1,a1[0]);
// 1 2 2
我感觉这代码不喝两瓶啤酒写不出来
但是这种代码是有可能写出来的
#include <vector>
#include <iostream>
#include <iterator>
#include <string>
#include <sstream>
#include <cstdio>
using namespace std;
void inline print_vector(const std::vector<int> & v)
{
ostringstream oss;
copy(v.begin(), v.end(), std::ostream_iterator<int>(oss, " "));
printf("%s\n", oss.str().c_str());
}
int main()
{
vector<int> vec {1,2,3,4,5,6,1,2,3,4,5,6,1,2,3,4,5,6};
vec.erase(std::remove(
begin(vec), end(vec),
*std::max_element(begin(vec), end(vec))),
end(vec));
print_vector(vec);
// 1 2 3 4 5 1 2 3 4 5 6 2 3 4 5 6
}
引用的位置对应的值变了,第一组1 2 3 4 5 6删掉了6,第二组的1补位,逻辑变成remove 1,然后2 3 4 5 6没删除,然后第三组 1 2 3 4 5 6,找到了1,最终就是这个效果
只能说,写代码的的时候少喝点酒
如何解决这个问题?把指针转换成值,强转一下,去掉指针信息,或者用decay_copy,原理都是一样的
template <class T>
typename std::decay<T>::type
decay_copy (T&& t) {
return std::forward<T>(t);
}
传引用反而比传值慢 godbolt
计算,传引用,寄存器利用效率不高,性能差, 用不上向量化
void byRef(std::vector<double>& v, const double& coeff) {
for (auto& i : v) i *= std::sinh(coeff);
}
void byVal(std::vector<double>& v, double coeff) {
for (auto& i : v) i *= std::sinh(coeff);
}
其实这背后有个问题,就是指针暗示着可能改动,所以不能尽可能 的优化,所以c中有restrict关键字,告诉你,这个指针在这个范围内不会被改,让编译器大胆做优化
作者还介绍了herb的一些实践,使用concept约束参数,以及思考std::ref stdx::val的用法等等。不过上面的代码例子是比较有意思值的看的了
python实现RAII
class Greeter:
def __init__(self, name):
self.name = name
print(f"hello, {self.name}!")
def __enter__(self):
return self
def __exit__(self, e_type, e_val, e_tb):
print(f"goodbye, {self.name}!")
def main():
with Greeter(1):
print("we have a greeter")
main()
有了RAII,一个scopeguard就有了
class DtorScope:
def __init__(self):
self.stack = []
def __enter__(self):
return self
def __exit__(self, e_type, e_val, e_tb):
while self.stack:
self.stack.pop().__exit__(e_type, e_val, e_tb)
def push(self, cm):
self.stack.append(cm)
然后可以结合闭包,装饰器模式
def cpp_function(f):
def _wrapper(*args, **kwargs):
with DtorScope():
return f(*args, **kwargs)
return _wrapper
这样就直接装饰main就行了
main = cpp_function(main)
main()
或者直接
@cpp_function
def main():
...
等一下,我们是c++周报,后面不展开了
看官方的例子
虽然是rust写的,但是是c++代码分析工具,所以放在这里了
TODO:有没有可能用c++重写?