在C++中声明自定义的类,编译器会默认帮助程序员生成一些未自定义的成员函数。这样的函数被称为“默认函数”。
Big-Five : 默认构造函数,析构函数, 拷贝构造函数, 拷贝赋值运算符、移动构造函数和移动赋值运算符。
拷贝操作:拷贝构造函数、拷贝赋值运算符。
移动操作:移动构造函数、移动赋值运算符
一旦程序员实现了这些函数的自定义版本,则编译器不会再为这类自动生成默认版本。(又称特种成员函数)
类默认函数的控制:“=default” 和“=delete” 函数
“=default”, 显式地指示编译器生成该函数的默认版本。
“=delete”,会指示编译器不生成函数的缺省版本。一旦缺省版本被删除了,重载该函数也是非法的。
默认构造函数是可以无实参调用的构造函数(以空参数列表定义,或为每个形参提供默认实参而定义)。拥有公开默认构造函数的类型,是可默认构造的。
仅当类中不包含用户声明的构造函数时,才生成。
示例一:没有定义默认构造函数,创建对象时,隐式生成一个默认构造函数
class X {
private:
int a;
};
X x; //可以编译通过
自动生成的默认构造函数没有参数,包含一个空的函数体,即 X::X(){ }
。
示例二:没有定义默认构造函数,自定义了非默认构造函数,出现编译错误
class X {
public:
X(int i) { a = i; } //用户自定义的构造函数
private:
int a;
};
X x; // 错误 , 默认构造函数 X::X() 不存在
不再隐式的生成默认构造函数,如果需要用默认构造函数来创建类的对象,必须显式定义。
示例三:手动定义默认构造函数
class X {
public:
X(){}; // 手动定义默认构造函数
X(int i) { a = i; }
private:
int a;
};
X x; // 正确,默认构造函数 X::X() 存在
问题:
示例四:defaulted函数,编译器自动生成默认构造函数
class X {
public:
X() = default;
X(int i) { a = i; }
private:
int a;
};
X x;
在C++11新标准中,如果我们需要默认的行为,那么可以通过在参数列表后面写上=default来要求编译器生构造函数
示例五:使类型恢复为POD类型
POD (Plain Old Data) 用于说明一个类型的属性,尤其是用户自定义类型的属性。
Plain 表示POD是个普通的类型,C++中常见的类型都有这样的属性。
Old 体现与C的兼容性。
#include <iostream>
#include <type_traits>
using namespace std;
class TwoCstor {
public:
//提供了带参数版本的构造函数,则必须自行提供不带参数版本,且TwoCstor不再是POD类型
TwoCstor() {}
TwoCstor(int i) : data(i) {}
private:
int data;
};
int main() {
cout << is_pod<TwoCstor>::value << endl; //0
}
#include <iostream>
#include <type_traits>
using namespace std;
class TwoCstor {
public:
//提供了带参数版本的构造函数,再指示编译器提供默认版本,则自定义类型依然是POD类型
TwoCstor() = default;
TwoCstor(int i) : data(i) {}
private:
int data;
};
int main() {
cout << is_pod<TwoCstor>::value << endl; //1
}
C++11,在函数的定义或者声明加上”=delete”,避免隐式数据类型转换。
示例六:避免隐式数据类型转换
class ConvType {
public:
ConvType(int i){};
ConvType(char c) = delete; //删除char 版本
};
void Func(ConvType ct) {}
int main() {
Func(3);
Func('a'); //无法通过编译
ConvType ci(3);
ConvType cc('a'); //无法通过编译
}
// 编译结果 error: use of deleted function ‘ConvType::ConvType(char)’
如果没有ConvType(char c) = delete; 编译器会隐式将’a’转化成int型,并调用int型的构造函数。
explicit关键字防止隐式转换。
示例七: 不建议将“=delete”与explicit合用
class ConvType {
public:
ConvType(int i){};
explicit ConvType(char c) = delete;
};
void Func(ConvType ct) {}
int main() {
Func(3);
Func('a'); //可以通过编译
ConvType ci(3);
ConvType cc('a'); //无法通过编译
}
析构函数时对象生存期终结时调用的特殊成员函数。析构函数的目的是释放对象可能在其生存期间获得的资源。
当一个对象超出作用域或执行delete的时候,析构函数就会被调用。
仅当基类的析构函数为虚的,派生类的析构函数才是虚的。
C++11,默认地,所有的析构函数(无论是用户定义的,还是编译器自动生成的)都隐式的具备noexcept性质。
(noexcept表示其修饰的函数不会抛出异常。如果noexcept修饰的函数抛出了异常,编译器可以选择直接调用std::terminate()函数来终止程序的运行)
示例八:自动隐式生成析构函数
class X {
private:
int x;
};
class Y : public X {
private:
int y;
};
int main() {
X *x = new Y;
delete x;
}
在销毁类X的对象的时候,需要调用类X的析构函数。没有为类X定义析构函数,编译器会自动隐式的为该类生成一个析构函数,自动生成的析构函数没有参数,包含一个空的函数体,即X::~X(){ }。
问题:内存泄漏
执行delete x的时候,系统调用类X的析构函数,编译器无法析构派生类的int型变量 y。
C++98:
将基类的析构函数定义为虚函数,利用delete语句删除指向派生类对象的基类指针时,系统会调用相应的派生类析构函数(实现多态性),从而避免内存泄漏。但是编译器隐式自动生成的析构函数都是非虚函数,因此需要手动为基类定义虚构函数。
示例九:手动定义基类虚析构函数
class X {
public:
virtual ~X(){}; // 手动定义虚析构函数
private:
int x;
};
class Y : public X {
private:
int y;
};
int main() {
X *x = new Y;
delete x;
}
问题:
自动生成->手动编写, 工作量加大
手动编写的析构函数的代码执行效率比编译器自动生成的析构函数低。
阻止了移动操作的生成。(大三律)
示例十:defaulted函数,编译器自动生成函数体。
class X {
public:
virtual ~X() = default; // 编译器自动生成 defaulted 函数定义体
private:
int x;
};
class Y : public X {
private:
int y;
};
int main() {
X *x = new Y;
delete x;
}
编译器会自动生成虚析构函数 virtual X::X(){}
,该函数比用户自己定义的虚析构函数具有更高的代码执行效率。
示例十一:需要对象在指定内存位置进行内存分配,并且不需要析构函数来完成一些对象级别的清理。
class X {
public:
~X() = delete;
private:
int x;
};
类T的复制构造函数时非模板构造函数,其首个形参为T&、const T& 、volatile T& 或const volatile T&, 而且要么没有其他形参,要么剩余形参均有默认值。
按成员进行非静态数据成员的拷贝构造。仅当类中不包含用户声明的拷贝构造函数时才生成。如果该类声明了移动操作,则拷贝构造函数将被删除。
拷贝构造函数被调用的场景:
一个对象需要通过另一个对象进行初始化
一个对象以值传递的方式作为参数传入函数
一个对象以值传递的方式作为返回值从函数返回
示例:自动生成默认拷贝构造函数
class X {
public:
X();
};
int main() {
X x1;
X x2 = x1; // 正确,调用编译器隐式生成的默认拷贝构造函数
}
没有显式声明定义对应类的拷贝构造函数,C++编译器会自动生成拷贝构造函数。
C++编译器提供的拷贝构造函数工作方式是浅拷贝
浅拷贝只是单纯拷贝了该成员的内存地址,但所指的空间内容没有被复制,而是由两个对象共用,容易出现double free的问题。
解决办法:1. 手动重载拷贝构造函数,实现深拷贝
2. 禁止编译器生成默认的拷贝构造函数, 拒绝不安全的浅拷贝
需求:禁止编译器生成默认的拷贝构造函数, 拒绝不安全的浅拷贝。
C++98,将拷贝构造函数声明为private的成员,并且不提供函数实现。
一旦有人试图(或者无意识)使用拷贝构造函数,编译器就会报错
示例十二:C++98 禁止使用拷贝构造函数
#include <iostream>
#include <type_traits>
using namespace std;
class NoCopyCstor {
public:
NoCopyCstor() = default;
private:
NoCopyCstor(const NoCopyCstor &);
//将拷贝构造函数声明为private成员并不提供实现,可以有效阻止用户错用拷贝构造函数
};
int main() {
NoCopyCstor a;
NoCopyCstor b(a); //无法通过编译
}
//编译结果:
//error: ‘NoCopyCstor::NoCopyCstor(const NoCopyCstor&)’ is private
C++11,在函数的定义或者声明加上”=delete”,会指示编译器不生成函数的缺省版本。
示例十三:C++11,禁止使用拷贝构造函数
#include <iostream>
#include <type_traits>
using namespace std;
class NoCopyCstor {
public:
NoCopyCstor() = default;
NoCopyCstor(const NoCopyCstor &) = delete;
//使用“=delete”同样可以有效阻止用户错用拷贝构造函数
};
int main() {
NoCopyCstor a;
NoCopyCstor b(a); //无法通过编译
}
//编译结果
// error: use of deleted function ‘NoCopyCstor::NoCopyCstor(const NoCopyCstor&)’
类T的复制赋值运算符是名为operator=的非模板非静态成员函数,它接收恰好一个T、T&、const T&、volatile T&或const volatile T &类型的形参。对于可复制赋值类型,它必须有公开的拷贝赋值运算符。
按成员进行非静态数据成员的拷贝赋值。仅当类中不包含用户声明的拷贝赋值运算符时才生成。
如果该类声明了移动操作,则复制构造函数将被删除。在已经存在复制构造函数或析构函数的条件下,仍然生成复制赋值运算符已经成为了被废弃的行为。
默认拷贝赋值操作符:浅拷贝
需要:1. 重载实现深度赋值
2.禁用拷贝赋值操作符
示例十四:自动生成默认拷贝赋值操作符
class X {
public:
X();
};
int main() {
X x1;
X x2 = x1; // 正确,调用编译器隐式生成的默认拷贝构造函数
X x3;
x3 = x1; // 正确,调用编译器隐式生成的默认拷贝赋值操作符
}
没有显式声明定义对应类的拷贝构造函数,C++编译器会自动生成拷贝赋值操作符,X &operator=(const X &){}。
示例十五:禁止类对象之间的赋值。
class X {
public:
X();
X(const X &) = delete; // 声明拷贝构造函数为 deleted 函数
X &operator=(const X &) = delete; // 声明拷贝赋值操作符为 deleted 函数
};
int main() {
X x1;
X x2 = x1; // 错误,拷贝构造函数被禁用
X x3;
x3 = x1; // 错误,拷贝赋值操作符被禁用
}
拷贝构造函数 VS. 拷贝赋值运算符
拷贝构造函数在创建或初始化对象的时候调用;
拷贝赋值运算符在更新一个对象的值时调用;
两种拷贝操作是彼此独立的。如果声明了一个拷贝构造函数,未声明拷贝赋值运算符,在进行拷贝赋值的时候,编译器会自动生成拷贝赋值运算符。同理,如果声明了一个拷贝赋值运算符,未声明拷贝构造函数,进行拷贝构造的时候,编译器会自动生成拷贝构造函数。
大三律(Rule of Three)
如果你声明了拷贝构造函数,拷贝赋值运算符,或析构函数中的任何一个,你就得同时声明所有的三个。
标准库中用以管理内存的类(如执行动态内存管理的STL容器)都会遵从大三律。
类T的移动构造函数时非模板构造函数,其首个形参是T &&、const T&&、volatile T&& 或 const volatile T&&, 且无其他形参,或剩余形参均有默认值
如果现有的对象是左值(lvalue),那么是拷贝构造函数,如果现有的对象是右值(rvalue)(例如,即将被摧毁的临时对象),那么是移动构造函数。
IntCell B = C; // 如果C是左值,那么是拷贝构造函数;如果C是右值,那么是移动构造函数
IntCell B { C }; // 如果C是左值,那么是拷贝构造函数;如果C是右值,那么是移动构造函数
class Widget {
public:
Widget(Widget &&rhs); //移动构造函数
Widget &operator=(Widget &&rhs); //移动赋值运算符
};
两种移动操作并不彼此独立,声明了其中一个,就会阻止编译器生成另外一个。
一旦显式声明了拷贝操作,就不会再生成移动操作了。
一旦声明了移动操作,编译器就会废除拷贝操作。(“=delete”)
受到大三律的影响:用户只要声明了析构函数,就不会生成移动操作。
移动操作生成条件(三者同时成立):
示例一:defaulted函数既可以在类体内(inline)定义,也可以在类体外(out-of-line)定义。
class X {
public:
X() = default; // Inline defaulted 默认构造函数
X(const X &);
X &operator=(const X &);
~X() = default; // Inline defaulted 析构函数
};
X::X(const X &) = default; // Out-of-line defaulted 拷贝构造函数
X &X::operator=(const X &) = default; // Out-of-line defaulted, 拷贝赋值操作符
示例二:defaulted函数的错误用法
class X {
public:
int f() = default; // 错误 , 函数 f() 非类 X 的特殊成员函数
X(int) = default; // 错误 , 构造函数 X(int, int) 非 X 的特殊成员函数
X(int = 1) = default; // 错误 , 默认构造函数 X(int=1) 含有默认参数
};
示例三:deleted函数,禁用某些用户自定义的类new操作符,避免在自由存储区创建类的对象。
#include <cstddef>
using namespace std;
class X {
public:
void *operator new(size_t) = delete;
void *operator new[](size_t) = delete;
};
int main() {
X *pa = new X; // 错误,new 操作符被禁用
X *pb = new X[10]; // 错误,new[] 操作符被禁用
}
示例四:deleted函数,用于普通函数禁止类型转换
#include <iostream>
using namespace std;
int add(int, int) = delete;
double add(double a, double b) { return a + b; }
int main() {
cout << add(1, 3) << endl; // 错误,调用了 deleted 函数 add(int, int)
cout << add(1.2, 1.3) << endl;
return 0;
}
delete禁用的仅是函数的定义,即该函数不能被调用,但是函数标识符add仍然有效,在名字查找和函数重载解析时仍会找到该函数标识符。
示例五:成员函数模板在任何情况下都不会抑制特种成员函数的生成。
class Widget {
public:
template <typename T>
Widget(const T &rhs); //以任意型别构造Widget
template <typename T>
Widget &operator=(const T &rhs); //以任意型别对Widget赋值
};
“=default” 修饰的函数为显式缺省(explicit defaulted)函数。仅适用于类的特殊成员函数,且该特殊成员函数没有默认参数。
“=delete” 修饰的函数为删除函数。
功能:
“=default” 使类型恢复为POD类型。(示例五)
2. “=default”,不仅可以用于类的定义中修饰成员函数,也可以在类定义之外修饰成员函数。在类定义外显式指定缺省版本所带来的好处是,可以对一个class定义提供多个实现版本。(补充示例一)
3. C++11标准并不要求编译器为某些函数提供缺省的实现,但如果将其声明为显式缺省的话,则编译器会按照某些“标准行为”为其生成所需要的版本。比如:“operator ==”。
4. "=delete"可以避免用户使用一些不应该使用的类的成员函数(示例十三)。
参考链接:
[1]https://www.cnblogs.com/lsgxeva/p/7787438.html
Benchmark: 程序名称
Time: 迭代一次的平均用户时间
CPU:迭代一次的平均CPU时间
Iterations: 总迭代次数
允许三种格式的输出,console、json、csv, 默认为console, 使用–benchmark_format=<console | json | csv>指定。 |
使用–benchmark_out=<filename>可将结果输出到指定的文件中。
传递和获取参数:
1.传递参数使用BENCHMARK宏生成的对象的Arg方法
2.传递进来的参数会被放入state 对象内部存储,通过range方法获取,调用时的参数0对应第一个参数
3.Arg 方法一次只能传递一个参数,使用Args方法传递多个参数
在benchmark里面,如果每个迭代会有一些额外的setup, 可能会需要在循环里面做。但是一般来说在benchmark时间统计里面把这部分去掉。有两个函数可以做这个事情:PauseTiming()和ResumeTiming()事实上在循环里面使用这两个函数,输出结果里面时间额外多了很多。
Time是一次操作的用户时间
CPU是一次操作的CPU时间
https://www.cnblogs.com/apocelipes/p/10348925.html
c++性能测试工具:google benchmark入门(一)
CPU指令集分为两种:
CISC:复杂指令集,指令多,功能多,一条指令可以完成很复杂的逻辑或者算数运算。
RISC:简单指令集,指令少,功能也少,但是基本上都是常用的指令,对于复杂的问题,需要通过N多个条指令才能完成,执行效率没有CISC高。
x86架构基于CISC,arm架构基于RISC。
在单处理器系统中,中断只会发生在指令与指令之间,能够在单条指令中完成的操作都可以认为是原子操作。X86使用CISC指令集,允许在一条指令里进行两次内存操作,因此,对于i++,i–这类操作在单处理器中可视为原子操作(必须显示使用addl r,%1命令)。
在多处理器系统中,多个处理器独立运行,在单条指令中能完成的操作也可能受到干扰,这种情况下,CPU提供了在指令执行期间对总线加锁的手段,使得同一总线上别的CPU就暂时不能通过总线访问内存了,保证了这条指令在多处理器环境中的原子性。
实现时使用了C内嵌汇编语言。
内嵌汇编语法:
__asm__:内嵌汇编关键字,告知编译器下述语句为汇编代码
__volatile__:告知编译器不要优化(比如重组优化)下述汇编语句
变量列表中常见符号:
“+”:操作数可读可写
“=”: 操作数只写
“&”:常用于输出操作,表示输出操作不能使用输入操作使用过的寄存器,只能+&或=&方式使用
“r”: 操作数是任何可用的通用寄存器
“m”: 操作数是内存变量
“p”: 操作数是一个合法的内存地址
“l”: 操作数是立即数
“Q”: A memory address which uses a single base register with no offset
“V”: 操作数是内存变量,但其寻址方式非偏移量类型
static __inline__ void atomic_ad(int i, atomic_t *v)
{
__asm__ __volatile__(
LOCK “addl %1,%0”
:"+m" (v->counter)
:"ir" (i),"m"(v->counter));
}
0% —–i
1% —–v->counter
ARM使用RISC指令集,在一次指令执行期间只能有一次内存操作,因此在单处理器系统中,i++, i–等也不能视为原子操作,ARM使用关中断来实现原子操作。
在ARM多核处理器中,使用专门的指令ldrex和strex实现原子操作。
static inline int atomic_add_return(int i, atomic_t *v)
{
unsigned long tmp;
int result;
__asm__ __volatile__("@ atomic_add_return\n"
"1: ldrex %0, [%3]\n"
" add %0,%0,%4\n"
" strex %1,%0,[%3]\n"
" teq %1, #0\n"
" bne 1b"
: "=&r" (result), "=&r" (tmp), "+Qo" (v->counter)
: "r" (&v->counter), "Ir"(i)
: "cc");
return result;
}
%0 —— result
%1 —— tmp
%3 —— v->counter的地址
%4 —– i
(1)ldrex %0,[%3]
独占式地加载(Load-Exclusive)v->counter的地址,把它的值放到result中,并更新exclusive monitor(s)
用C描述就是:
result = v->counter
(2)add %0,%0,%4
result = result + i
(3) strex %1, %0, [%3]
独占式地保存(Store-Exclusive)数据至v->counter的地址,数据来自result, 操作结果(成功/失败)保存在tmp中
“cc”是一个特殊的参数,用来标明汇报代码会修改标志寄存器(flags register),在某些机器平台上,GCC通过一个特殊的硬件寄存器表征条件类型的代码,“cc”就是这个特殊寄存器的名字,某些机器平台没有上述功能,“cc”会被忽略,不起作用。
sysbench是一个开源的、模块化的、跨平台的多线程性能测试工具,可以用来进行CPU、内存、磁盘I/O、线程、数据库的性能测试。目前支持的数据库有MySQL、Oracle和PostgreSQL。当前功能允许测试的系统参数有:
file I/O performance (文件I / O性能)
scheduler performance (调度性能)
memory allocation and transfer speed (内存分配和传输速度)
POSIX threads implementation performance (POSIX线程执行绩效)
database server performance (OLTP benchmark) (数据库服务器性能)
下载sysbench的源代码:
https://github.com/akopytov/sysbench/archive/1.0.17.zip
将sysbench-1.0.17.zip上传到安装目录。
使用如下命令解压:
unzip sysbench-1.0.17.zip
进入sysbench-1.0.17目录中
cd sysbench-1.0.17/
然后执行
./autogen.sh
然后执行如下命令
./configure –with-mysql-includes=/home3/mysql/include –with-mysql-libs=/home3/mysql/lib
Sysbench编译时需要通过 –-with-mysql-includes 和 –with-mysql-libs 选项对相关路径进行指定。
然后就可以执行
make 和make install
fileio
cpu
memory
threads
mutex
查看CPU相关信息:cat /proc/cpuinfo
./sysbench –test=cpu –cpu-max-prime=2000 run
./sysbench –test=threads –num-threads=500 –thread-yields=100 –thread-locks=4 run
seqwr顺序写入
seqrewr 顺序重写
seqrd 顺序读取
rndrd 随机读取
rndwr 随机写入
rndrw 混合随机读、写
./sysbench –test=fileio –num-threads=16 –file-total-size=2G –file-test-mode=rndrw prepare
./sysbench –test=fileio –num-threads=16 –file-total-size=2G –file-test-mode=rndrw run
./sysbench –test=fileio –num-threads=16 –file-total-size=2G –file-test-mode=rndrw cleanup
./sysbench –test=memory –memory-block-size=8k –memory-total-size=1G run
查找帮助:
./sysbench –test=mutex help
./sysbench –test=mutex –num-threads=100 –mutex-num=1000 –mutex-locks=100000 –mutex-loops=10000 run
sysbench 0.5 oltp测试结合了lua脚本
1) Prepare 操作。在这个步骤中,会对将要进行的测试进行数据准备,包括创建表和向表中插入数据。
2) Run 操作。主要测试步骤,在这个步骤中可以指定多种选项来,以对不同的测试情况进行模拟。
3) Cleanup 操作。将测试数据删除,主要是删除表。
./sysbench –test=/home/lzy/code/src/tools/sysbench0.5/share/sysbench/oltp.lua –mysql-host=127.0.0.1 –mysql-port=33071 –mysql-user=root –mysql-password= –mysql-db=test –mysql-table-engine=innodb –oltp-table-size=25000 –oltp-tables-count=50 –db-driver=mysql prepare
./sysbench –test=/home/lzy/code/src/tools/sysbench0.5/share/sysbench/oltp.lua –mysql-host=127.0.0.1 –mysql-port=33071 –mysql-user=root –mysql-password= –mysql-db=test –mysql-table-engine=innodb –oltp-table-size=25000 –oltp-tables-count=50 –db-driver=mysql run
/sysbench –test=/home/lzy/code/src/tools/sysbench0.5/share/sysbench/oltp.lua –mysql-host=127.0.0.1 –mysql-port=33071 –mysql-user=root –mysql-password= –mysql-db=test –mysql-table-engine=innodb –oltp-table-size=25000 –oltp-tables-count=50 –db-driver=mysql cleanup
–test=tests/db/oltp.lua 表示调用 tests/db/oltp.lua 脚本进行 oltp 模式测试
–mysql-table-engine=innodb 表示选择测试表的存储引擎 –oltp_tables_count=10 表示会生成 10 个测试表 –oltp-table-size=100000 表示每个测试表产生的记录行数为 100000 –rand-init=on 表示每个测试表都是用随机数据来填充的 –oltp-read-only=off:表示不止产生只读SQL,也就是使用oltp.lua时会采用读写混合模式。默认 off,如果设置为on,则不会产生update,delete,insert的sql –num-threads=8 表示发起 8个并发连接
–oltp-test-mode=nontrx:执行模式,这里是非事务式的。可选值有simple,complex,nontrx。默认是complex
–oltp-skip-trx=[on | off]:省略begin/commit语句。默认是off |
–report-interval=10 表示每10秒输出一次测试进度报告 –rand-type=uniform 表示随机类型为固定模式,其他几个可选随机模式:uniform(均匀分布),gaussian(高斯分布),special(空间分布),pareto(帕累托),默认为special –max-time=120 表示最大执行时长为 120秒 –max-requests=0 表示总请求数为 0,因为上面已经定义了总执行时长,所以总请求数可以设定为 0;也可以只设定总请求数,不设定最大执行时长 –percentile=99 表示设定采样比例,默认是 95%,即丢弃1%的长请求,在剩余的99%里取最大值
OLTP test statistics:
Queries performed:
read: 140000 //读总数
write: 40000 //写总数
other: 20000 //其他操作总数(SELECT、INSERT、UPDATE、DELETE之外的操作,例如COMMIT等)
total: 200000 //全部总数
transactions: 10000 (30.04 per sec.) // TPS 总事务数(每秒事务数)
read/write requests: 180000 (540.77 per sec.) //QPS 读写总数(每秒读写次数)
other operations: 20000 (60.09 per sec.)//其他操作总数(每秒其他操作次数)
ignored errors: 0 (0.00 per sec.) //忽略的错误数
reconnects: 0(0.00 per sec.)
General statistics:
total time: 332.8696s //总耗时。可以通过max-time参数指定
total number of events: 10000 //总事件数,一般与transactions相同
total time taken by event execution: 332.8291s //所有事务耗时相加(不考虑并行因素),如果不并发执行需要花的时长
response time:(单个请求的时间)
min: 15.24ms //最小耗时
avg: 33.28ms //平均耗时
max: 60018.48ms //最大耗时
approx. 95 percentile: 40.64ms //超过95%平均耗时
Threads fairness:
events (avg/stddev): 10000.0000/0.00
execution time (avg/stddev): 332.8291/0.00
· TPS:Transaction Per Second,数据库每秒执行的事务数,每个事务中包含18条SQL语句。
· QPS:Query Per Second,数据库每秒执行的SQL数,包含insert、select、update、delete等。
1) common.lua 脚本。这个公用的脚本,所有其他的脚本(除了select_random_points.lua和select_random_ranges.lua )都加载了这个脚本。在这个脚本中主要实现了 prepare 命令和 cleanup 命令的功能。而其他脚本就对应了具体 run 令时要做的工作。所以在 prepare 和 run 命令时,可以选用任意一个脚本运行,但是需要指定除表的个数,即 oltp-tables-count 选项。创建的表的结构(MySQL数据库)
2) select.lua 脚本。这个脚本是在表中进行单点查询。(随机用的是uniform策略)
3) insert.lua 脚本。这个脚本是在表中进行 insert 操作。(随机用的是uniform策略)
4) delete.lua 脚本。这个脚本是在表中进行 delete 操作。(随机用的是uniform策略)
5) parallel_prepare.lua 脚本。这个脚本是多线程创建表,根据线程数选项,开多个线程进行数据库表的创建。
6) oltp_simple 脚本。同 select.lua。
7) oltp.lua 脚本。如果数据库支持事务,就使用 BEGIN/COMMIT语句,如果不支持,就使用 LOCK TABLES/UNLOCK TABLES 语句,可以使用 –oltp-skip-trx 选项,跳过。在这个模式中,操作不会对原本的数据库产生影响,因为插入一个数据,之后就会立即删除这个数据,所以数据库可以重复使用。可以指定在一次事务中,指定单点查询、简单范围查询(between)、聚集函数 SUM()、Order by、distinct c、索引更新或非索引更新语句的数目。
8) update_index.lua 脚本。这个脚本是对表中的索引字段进行更新。如果基准测试的时候,你只想比较两个项目的update(或insert)效率,那可以不使用oltp脚本,而直接改用update_index.lua
9) update_non_index.lua 脚本。这个脚本对表中的非索引字段进行更新。
指定参数的函数实现,通过指定的参数名访问
不指定参数的函数实现,函数调用的参数进行压栈处理(从右到左进行压栈)
可变参数函数:参数个数可变、参数类型不定的函数
“…” 表示0个或多个类型未知的参数
最常见的例子:
int printf(const char * format, ...)
调用:
int a=5;
char b='b';
printf("%d and %c",a,b);
参数压栈顺序:b,a,format
函数调用内存结构:
对不定参数部分用”…“表示
可变参数至少包含一个参数,用来寻址,实现对所有参数的访问
已知的指定参数必须声明在函数最左端
错误的声明:
void func(...)
或者
void func(..., int a);
求和。第一个参数指定要计算的值的个数
格式化字符串。第一个参数指定占位符
#include <iostream>
#include <cstdarg>
#include <cstdio>
#include <vector>
using namespace std;
int sum(int num,...) //利用变长函数进行求和运算
{
int sumval=0;
va_list args; //定义一个可变参数列表
va_start(args,num); //初始化args指向强制参数arg的下一个参数
while(num--)
{
sumval+=va_arg(args,int); //获取参数的值
}
va_end(args); //释放args
return sumval;
}
string format(const char* format, ...) //格式化字符串
{
string var_str;
va_list ap;
va_start(ap, format);
int len = _vscprintf(format, ap);
if (len > 0)
{
vector<char> buf(len + 1);
vsprintf(&buf.front(), format, ap);
var_str.assign(buf.begin(), buf.end() - 1);
}
va_end(ap);
return var_str;
}
int main()
{
cout<<sum(5,10,23,78,65,9)<<endl;
cout<<sum(8,1,2,3,4,5,6,7,8)<<endl;
cout<<format("%s#%s#%s","this","is","me");
//cout<<sum(5,10.23,23.78,78.59,65.12,9.08)<<endl;
return 0;
}
参数类型不匹配,程序会出错,可能导致程序崩溃。
“…” 表示0个或多个类型未知的参数,于是可以帮助我们完成递归
#include <iostream>
#include <bitset>
using namespace std;
void print()
{
}
template <typename T,typename... Types> //...用于模板参数
void print(const T& firstArg, const Types&... args)//...用于函数参数类型
{
cout<<firstArg<<endl;
print(args...);//...用于函数参数
}
int main()
{
print(7.5, "hello", bitset<16>(377),42);
return 0;
}
运行结果:
template<typename... Types>
void print(const Types&... args)
{
}
案例1:
class CustomerHash{
public:
std::size_t operator()(const Customer& c) const{
return hash_val(c.fname,c.lname,c.no);
}
};
template<typename...Types>
inline size_t hash_val(const Types&... args) //函数1
{
size_t seed=0;
hash_val(seed,args...);
return seed;
}
template<typename T, typename... Types>
inline void hash_val(size_t& seed, const T& val, const Types&... args) //函数2
{
hash_combine(seed,val);
hash_val(seed,args...);
}
template <typename T>
inline void hash_val(size_t& seed, const T& val) //函数3
{
hash_combine(seed,val);
}
template <typename T>
inline void hash_combine(size_t& seed, const T&val) //函数4
{
seed^=std::hash<T>()(val)+0x9e3779b9+(seed<<6)+(seed>>2);
}
案例2:
template<typename... Values> class tuple;
template<>class tuple<>{};
template<typename Head,typename... Tail>
class tuple<Head,Tail...>:private tuple<Tail...>
{
typedef tuple<Tail...> inherited;
public:
tuple(){}
tuple(Head v,Tail... vtail):m_head(v),inherited(vtail...){}
typename Head::type head(){return m_head;}
inherited& tail(){return *this;}
protected:
Head m_head;
};
tuple<int,float,string> t(41,6.3,"nico");
t.head() //41
t.tail() //6.3, nico
t.tail().head() //6.3
&(t.tail) //nico
…就是一个所谓的包(pack)
用于template parameters,就是template parameters pack (模板参数包)
用于function parameter types,就是function parameter types pack(函数参数类型包)
用于function parameters,就是function parameters pack(函数参数包)