Benchbox是一个基准测试包,基于tbox和xmake,里面包含许多针对第三方库功能的性能基准测试和对比,可以很方便的扩展测试用例和模块。
目前内置:各大开源协程库性能基准测试,后续还会陆续增加各种开源库模块的分析测试
测试报告仅供参考,测试代码或者结果上如有问题,可以提交issues
请先安装: xmake
然后运行:
$ xmake
$ xmake coroutine -n switch
tbox: 10000000 switches in 205 ms, 48780487 switches per second
boost: 10000000 switches in 728 ms, 13736263 switches per second
libmill: 10000000 switches in 525 ms, 19047619 switches per second
libtask: 10000000 switches in 1602 ms, 6242197 switches per second
golang: 10000000 switches in 1558 ms, 6418485 switches per second
xmake的工程描述文件xmake.lua虽然基于lua语法,但是为了使得更加方便简洁得编写项目构建逻辑,xmake对其进行了一层封装,使得编写xmake.lua不会像些makefile那样繁琐
基本上写个简单的工程构建描述,只需三行就能搞定,例如:
target("test")
set_kind("binary")
add_files("src/*.c")
然后只需要执行编译并且运行它:
$ xmake run test
这对于想要临时写些测试代码来讲,极大地提升了开发效率。。
xmake的描述语法是按作用域划分的,主要分为:
原子操作,线程间交互数据最细粒度的同步操作,它可以保证线程间读写某个数值的原子性。
由于不需要加重量级的互斥锁进行同步,因此非常轻量,而且也不需要在内核间来回切换调度,效率是非常高的。。
那如何使用原子操作了,各个平台下都有相关api提供了支持,并且向gcc、clang这些编译器,也提供了编译器级的__builtin接口进行支持
__sync_val_compare_and_swap
和__sync_val_compare_and_swap_8
等__builtin接口lock
汇编指令先拿tbox的tb_atomic_fetch_and_add
接口为例,顾名思义,这个api会先读取原有数值,然后在其基础上加上一个数值:
// 相当于原子进行:b = *a++;
tb_atomic_t a = 0;
tb_long_t b = tb_atomic_fetch_and_add(&a, 1);
如果需要先进行add计算,再返回结果可以用:
// 相当于原子进行:b = ++*a;
tb_atomic_t a = 0;
tb_long_t b = tb_atomic_add_and_fetch(&a, 1);
或者可以更加简化为:
tb_long_t b = tb_atomic_fetch_and_inc(&a);
tb_long_t b = tb_atomic_inc_and_fetch(&a);
线程局部存储(Thread Local Storage,TLS)主要用于在多线程中,存储和维护一些线程相关的数据,存储的数据会被关联到当前线程中去,并不需要锁来维护。。
因此也没有多线程间资源竞争问题,那如何去实现TLS存储呢,主要有以下几种方式:
__thread
修饰符__declspec(thread)
修饰符pthread_setspecific
和pthread_getspecific
接口TlsSetValue
和TlsGetValue
其中__thread和__declspec(thread)用起来最为方便,只需要在static或者全局变量前加上此修饰符,然后在线程里面访问变量就行了
例如:
tb_void_t tb_thread_func(tb_cpointer_t priv)
{
// 定义一个线程局部变量
static __thread int a = 0;
// 初始化这个变量,设置为当前线程id
if (!a) a = tb_thread_self();
}
tbox目前支持sqlite3、mysql两种关系型数据库(需要链接对应的libsqlite3.a和libmysql.a),并对其接口进行了封装,使用更加的方便简洁并且只需要换个url,就可以随时切换成其他数据库引擎,而不需要修改接口。
下面先看个简单的例子:
/* 初始化一个mysql数据库
*
* localhost: 主机名,也可以是ip地址
* type: 数据库的类型,目前支持:mysql 和 sqlite3两种类型
* username: 数据库用户名
* password: 数据库用户密码
* database: 要访问的数据库名称, 如果不设置则自动连接默认的数据库
*
* 若果你想指定个端口,可以显示传入:
* "sql://localhost:3306/?type=mysql&username=xxxx&password=xxxx&database=xxxx"
*
* sqlite3的数据库访问url为:
* "sql:///home/file.sqlitedb?type=sqlite3"
*
* 或者直接传文件路径:
* "/home/file.sqlite3"
* "file:///home/file.sqlitedb"
* "C://home/file.sqlite3"
*/
tb_database_sql_ref_t database = tb_database_sql_init("sql://localhost/?type=mysql&username=xxxx&password=xxxx&database=test");
if (database)
{
// 打开数据库
if (tb_database_sql_open(database))
{
// 执行sql语句,进行查询
if (tb_database_sql_done(database, "select * from test"))
{
/* 加载结果集
*
* 如果是insert, update等非select语句执行,这个时候返回tb_null, 说明没有结果集
*
* 这个结果集完全采用迭代器模式,方便快速迭代访问。
*
* 后面的参数tb_true指示尽量一次性加载所有结果到内存,如果成功
* 就可以通过tb_iterator_size(result)获取实际的结果集行数。
*
* 如果一次性加载失败或者传入的是tb_false, 说明只能通过fetch,一行行的回滚数据行
* 这样占用内存的资源较少,但是没法提前获取到实际的行数,这个时候tb_iterator_size(result)
* 返回的是一个超大值
*/
tb_iterator_ref_t result = tb_database_sql_result_load(database, tb_true);
if (result)
{
// 如果一次性加载成功,返回实际的结果行数
tb_trace_i("row: size: %lu", tb_iterator_size(result));
// 遍历所有行
tb_for_all_if (tb_iterator_ref_t, row, result, row)
{
// 显示行的列数
tb_trace_i("[row: %lu, col: size: %lu]: ", row_itor, tb_iterator_size(row));
// 遍历这一行中的所有值
tb_for_all_if (tb_database_sql_value_t*, value, row, value)
{
/* tb_database_sql_value_name(value): 获取值所对应列名
* tb_database_sql_value_text(value):获取值的文本数据,如果是text类型的话
* tb_database_sql_value_type(value):获取值的类型
*
* ...
*/
tb_tracet_i("[%s:%s] ", tb_database_sql_value_name(value), tb_database_sql_value_text(value));
}
}
// 释放结果集数据
tb_database_sql_result_exit(database, result);
}
}
else
{
// 执行失败,显示失败状态和原因
tb_trace_e("done %s failed, error: %s", sql, tb_state_cstr(tb_database_sql_state(database)));
}
}
else
{
// 打开失败,显示失败状态和原因
tb_trace_e("open failed: %s", tb_state_cstr(tb_database_sql_state(database)));
}
// 退出数据库
tb_database_sql_exit(database);
}