redis-list队列功能,生产-消费模式,大多用于项目的事件驱动,本文将最近测试的REDIS列表与STVM无锁环形队列性能做以下测试报告,仅供大家参考。
由于STVM本身支持多进程,多线程,为了测试对比性,STVM网络服务启动1个线程,单张队列表,客户端采用多进程,多线程,全部采用单机部署。
注意gcc 版本必须 >= 4.1.2
测试配置:
机器配置如下:
在64位 Ubuntu上测试,机器配置如下:
4G RAM,CPU:Intel(R) Core(TM) i3 CPU M 380 @ 2.53GHz 4核
测试案例:
本地调用
由于redis不支持本地调用接口,因此在本地调用仅展示数据。
- STVM多线程本地PUSH数据
线程数 | 400W记录5次平均耗时(单位秒) | TPS |
1 | 2.772 | 1,442,897 |
2 | 1.944 | 2,057,825 |
4 | 1.678 | 2,383,790 |
10 | 1,608 | 2,487,253 |
- STVM多进程本地PUSH数据:
进程数 | 400W记录5次平均耗时(单位秒) | TPS |
2 | 2.772 | 1,993,223 |
4 | 1.944 | 2,326,664 |
5 | 1.678 | 2,322,341 |
结果分析:
线程效率跟进程效率相差不大,因为是原子锁(CAS),多线程下存在少量优势。
- STVM多线程本地POP数据
线程数 | 400W记录5次平均耗时(单位秒) | TPS |
1 | 3.049 | 1,311,992 |
2 | 2.190 | 1,826,651 |
4 | 1.926 | 2,077,059 |
10 | 1.867 | 2,142,934 |
- STVM多进程本地POP数据
进程数 | 400W记录5次平均耗时(单位秒) | TPS |
2 | 2.137 | 1,872,133 |
4 | 1.828 | 2,188,432 |
5 | 1.844 | 2,169,668 |
结果分析:
线程效率跟进程效率相差不大,因为是原子锁(CAS),多线程下存在少量优势。
- STVM多线程本地PUSH-POP数据
线程数 | 800W记录5次平均耗时(单位秒) | TPS |
1-POP 1-PUSH | 3.507 | 2,281,152 |
1-POP 2-PUSH | 2.933 | 2,727,583 |
2-POP 2-PUSH | 2.577 | 3,104,385 |
PUSH 400W, POP 400记录,测试性能。
网络调用
考虑网络IO,redis队列的value的大小跟stvm单元记录大小一致。
- REDIS多线程同步PUSH数据
线程数 | 400W记录5次平均耗时(单位秒) | TPS |
1 | 186.201 | 21,482 |
2 | 94.468 | 42,342 |
4 | 81.217 | 49,251 |
- STVM多线程同步PUSH数据
线程数 | 400W记录5次平均耗时(单位秒) | TPS |
1 | 118.466 | 33,765 |
2 | 50.705 | 78,888 |
4 | 37.662 | 106,209 |
- REDIS多线程POP数据
线程数 | 400W记录5次平均耗时(单位秒) | TPS |
1 | 167.157 | 23,930 |
2 | 74.067 | 54,006 |
4 | 65.923 | 60,667 |
- STVM多线程POP数据
线程数 | 400W记录5次平均耗时(单位秒) | TPS |
1 | 120.650 | 33,154 |
2 | 53.878 | 74,241 |
4 | 37.622 | 106,322 |
- REDIS多线程异步PUSH
线程数 | 400W记录5次平均耗时(单位秒) | TPS |
1 | 50.289 | 79,289 |
2 | 46.163 | 86,649 |
4 | 39.649 | 100,885 |
- STVM多线程异步PUSH
线程数 | 400W记录5次平均耗时(单位秒) | TPS |
1 | 11.166 | 358,238 |
2 | 9.846 | 406,246 |
4 | 9.311 | 429,599 |
结果分析:
性能优势主要体现在CAS原子方法上。
在相同IO下 ,redis最大性能10W,stvm网络最大性能42W,STVM主要优势在于本地调用达100W+,多线程下可到达200W+,除外如果新增多个队列表,只要机器资源充足,性能将会倍数增长, 这对于单机群体优势明显。
附上一个push和pop多线程测试代码:
#include "tvm.h"
#define QUEUE_USER_INFO 21
typedef unsigned long long uint64;
extern uint64 get_tick_time();
typedef struct _ST_ARG_
{
uint64 rows;
uint64 start;
int threadIndex;
}ARG;
typedef struct __QUEUE_USER_INFO
{
long acct_id;
char user_no[21];
char user_type[2];
char user_nm[81];
char user_addr[161];
char user_phone[31];
}dbUser;
void* vPushQueue(void *arg)
{
int i;
dbUser stUser;
ARG *pArgInfo = (ARG *)arg;
SATvm *pstSavm = (SATvm *)pCloneSATvm();
queueinit(pstSavm, stUser, QUEUE_USER_INFO); // 绑定变量
strcpy(stUser.user_no, "20180223"); // 对结构体赋值
strcpy(stUser.user_type, "1"); // 对结构体赋值
strcpy(stUser.user_nm, "Savens Liu"); // 对结构体赋值
strcpy(stUser.user_addr, "China"); // 对结构体赋值
strcpy(stUser.user_phone, "18672911111"); // 对结构体赋值
for(i = 0; i < pArgInfo->rows; i ++)
{
stUser.acct_id = i; // 对结构体赋值
if(RC_SUCC != lPush(pstSavm))
{
fprintf(stderr, "Push error:(%d)(%s)\n", pstSavm->m_lErrno, sGetTError(pstSavm->m_lErrno));
return NULL;
}
}
return NULL;
}
int main(int argc, char *argv[])
{
uint64 uTime;
ARG arg[100];
pthread_t thread[10];
int i, j, rows = 0, num;
SATvm *pstSavm = (SATvm *)pGetSATvm();
if(argc < 2)
{
fprintf(stderr, "lost thread num\n");
return -1;
}
if(1 != argc)
num = strlen(argv[1])>0?atoi(argv[1]):1;
else
num = 1;
vHoldConnect(pstSavm);
if(RC_SUCC != lAttchTable(pstSavm, QUEUE_USER_INFO))
{
fprintf(stderr, "attch failed, err:(%d)(%s)\n", pstSavm->m_lErrno, sGetTError(pstSavm->m_lErrno));
return RC_FAIL;
}
if(argc == 3)
rows = atol(argv[2]) / num;
else
rows = 4000000 / num;
for(i = 0, uTime = get_tick_time(); i < num; i++)
{
arg[i].threadIndex = i + 1;
arg[i].start = rows * i;
arg[i].rows = rows;
pthread_create(&thread[i], NULL, vPushQueue, (void*)&arg[i]);
}
for(j = 0; j < num; j++)
pthread_join(thread[j], NULL);
fprintf(stdout, "cost_time:[%llu]\r\n", get_tick_time() - uTime);
vHoldRelease(pstSavm);
return RC_SUCC;
}
#include "tvm.h"
#define QUEUE_USER_INFO 21
typedef unsigned long long uint64;
extern uint64 get_tick_time();
typedef struct _ST_ARG_
{
uint64 rows;
uint64 start;
int threadIndex;
}ARG;
typedef struct __QUEUE_USER_INFO
{
long acct_id;
char user_no[21];
char user_type[2];
char user_nm[81];
char user_addr[161];
char user_phone[31];
}dbUser;
void* vPopQueue(void *arg)
{
int i;
dbUser stUser;
ARG *pArgInfo = (ARG *)arg;
SATvm *pstSavm = (SATvm *)pCloneSATvm();
queueinit(pstSavm, stUser, QUEUE_USER_INFO); // 绑定变量
for(i = 0; i < pArgInfo->rows; i ++)
{
if(RC_SUCC != lPop(pstSavm, (void *)&stUser, QUE_NORMAL))
{
fprintf(stderr, "Pop error:(%d)(%s)\n", pstSavm->m_lErrno, sGetTError(pstSavm->m_lErrno));
return NULL;
}
}
return NULL;
}
int main(int argc, char *argv[])
{
uint64 uTime;
ARG arg[100];
pthread_t thread[10];
int i, j, rows = 0, num;
SATvm *pstSavm = (SATvm *)pGetSATvm();
if(argc < 2)
{
fprintf(stderr, "lost thread num\n");
return -1;
}
if(1 != argc)
num = strlen(argv[1])>0?atoi(argv[1]):1;
else
num = 1;
vHoldConnect(pstSavm);
if(RC_SUCC != lAttchTable(pstSavm, QUEUE_USER_INFO))
{
fprintf(stderr, "attch failed, err:(%d)(%s)\n", pstSavm->m_lErrno, sGetTError(pstSavm->m_lErrno));
return RC_FAIL;
}
if(argc == 3)
rows = atol(argv[2]) / num;
else
rows = 4000000 / num;
for(i = 0, uTime = get_tick_time(); i < num; i++)
{
arg[i].threadIndex = i + 1;
arg[i].start = rows * i;
arg[i].rows = rows;
pthread_create(&thread[i], NULL, vPopQueue, (void*)&arg[i]);
}
for(j = 0; j < num; j++)
pthread_join(thread[j], NULL);
fprintf(stdout, "cost_time:[%llu]\r\n", get_tick_time() - uTime);
vHoldRelease(pstSavm);
fflush(stdout);
return RC_SUCC;
}