[TOC] 前言 在上篇文章中,我们介绍了clickhouse的新特性——MergeTree启动加速,及其使用方法。本文将介绍MergeTree启动加速的设计原理。 MergeTree启动原理 part结构 我们知道,在clickhouse中,MergeTree表由一个个part组成。每个part对应一个目录,该目录下有两类文件:元数据文件,数据文件和projection part目录(如果该表创建了projection的话) $ ll ./20210321_310_310_0 total 36 -rw-r----- 1 root root 28 Feb 9 14:26 primary.idx -rw-r----- 1 root root 4 Feb 9 14:26 partition.dat -rw-r----- 1 root root 4 Feb 9 14:26 minmax_day.idx -rw-r----- 1 root root 10 Feb 9 14:26 default_compression_codec.txt -rw-r----- 1 root root 1648 Feb 9 14:26 data.mrk3 -rw-r----- 1 root root 2110 Feb 9 14:26 data.bin -rw-r----- 1 root root 1 Feb 9 14:26 count.

世面上将c++性能优化的书其实不少了,但是很多都停留在架构、算法、数据结构层面,大都是些老生常谈了。而从语言本身、操作系统、硬件层面系统阐述性能优化的技术书则少了很多。而《optimizing software in c++》正是这样的一本书,作者Agner Fog的职业也挺有意思,除了是计算机科学家,还是进化人类学家,而且后者看起来还是主业.. 这周末花了一天时间阅读了这本书中感兴趣的几个章节。笔者将会连载几篇读书笔记总结主要知识点。 7 The efficiency of different C++ constructs 8 Optimizations in the compiler 9 Optimizing memory access 11 Out of order execution 12 Using vector operations The efficiency of different C++ constructs 这章主要介绍c++语言中各种特性对性能的影响。 不同的变量存储位置 stack 众所周知,函数中的临时变量或对象一般存储在内存空间中的stack区。每当调用函数时,参数和临时变量进栈,当函数返回时,参数和临时变量出。s由于stack可被不断重复使用,栈是内存空间中最高效的存储方式。当临时变量中没有大对象时,访问栈上的临时变量也基本能用上L1 data cache. global or static 在函数体之外声明的变量称之为global变量,可被任何函数访问。被static修饰的变量称为static变量。 global和static变量在程序运行期间会被放置于内存空间中的静态数据区。静态数据区域分为三个部分:一部分存储const类型的global/static变量,一部分存储已被初始化的global/static变量,最后一部分存储未被初始化的global/static变量 使用静态数据区的好处是,global/static变量在程序启动前就有专门的存储位置,坏处是在程序的生命周期内,这些存储位置将被一直占据,可能会降低data cache的效率。 所以建议尽量不要使用global变量 register register变量存储在cpu寄存器中,函数中的临时变量特别适合放到register中。优点很明显,访问register变量比访问RAM快得多,但是cpu寄存器大小是非常有限的,在64位x86架构中,有: 14个整数寄存器 16个浮点寄存器 volatile volatile用于声明一个变量可被其他线程改变,阻止编译器依赖变量始终具有代码中先前分配的值的假设来进行优化 。 volatile int seconds; // incremented every second by another thread void DelayFiveSeconds() { seconds = 0; while (seconds < 5) { // do nothing while seconds count to 5 } } 上面的代码如果不声明为volatile, 编译器将任务while条件一直成立,即使别的线程中改变了seconds的值。

书接上回,继续阅读第七章(The efficiency of different C++ constructs) The efficiency of different C++ constructs 循环 循环的效率取决于微处理器对循环控制分支的预测能力。一个具有一个较小并且固定的重复计数,没有分支的循环,可以完美地被预测。 循环展开 展开前 int i; for (i = 0; i < 20; i++) { if (i % 2 == 0); FuncA(i); else FuncB(i); FuncC(i); } 展开后 int i; for (i = 0; i < 20; i+=2) { FuncA(i); FuncC(i); FuncB(i+1); FuncC(i+1); } 这样做的好处: 循环次数变成了10次而不是20次,CPU可以更完美的进行预测 if分支被消除,有利于编译器自动进行向量化等优化 循环展开的坏处: 展开循环后在代码缓存中占用更多空间 非常小的循环展开不如不展开 如果重复计数为奇数,并将其展开为2, 则必须在循环之外执行额外的迭代。