最近,在ACMUG(中国MySQL用户组)的年度活动中,较为详细对之前的云数据库 RDS MySQL性能测试做了分享。如下为分享的PDF,供参考:
(more…)admin
-
随着云数据库的普及,数据库的传输加密也会随之被广泛使用。本文,将使用通用测试来看看,开启TLS传输对数据库的性能有什么样的影响。
1. 原理概述
开启TLS传输加密之后,分别会在建立连接、数据传输两个阶段对性能造成影响。因为建立连接的过程通常是一次性的,连接会被复用,所以这部分的性能开销通常是可以接受的。在传输阶段,是传输加密对性能影响的重要阶段,这时候通常会使用对称加密算法(例如AES256)对数据进行加密,那么实际的对称加密的性能就是对TLS传输加密性能影响最大的部分。需要注意的是,如果应用使用的是短链接(应该尽量避免使用这种方式),TLS加密的密钥交换阶段也会对每个连接建立的过程都有一定的性能影响。
2. 云数据库实际测试
这里选择对RDS MySQL进行测试,云厂商则各自选择国外、国内Top 1的云厂商AWS和阿里云。
2.1 测试方法说明
使用sysbench 1.1.0版本,测试类型为oltp_read_write,相关参数如下:
- –mysql-ssl=REQUIRED 是否使用TLS加密传输
- –percentile=95 关注95%Query的延迟
- –histogram=on 可以查看延迟分布情况
- –time=600 sysbench运行的总时间(秒)
- –warmup-time=60 开始计算性能前预热时间(秒)
- –table_size=1000000 单表100万条记录
- –tables=5 共测试5个表
-
这是一个系列文章,旨在了解如何使用Flex/Lex和Yacc/Bison进行词法和语法解析。在前面,已经完成了使用Lex/flex做基础的词法解析、实现一个简单的计算器、flex/bison系列3:更复杂的一个编译程序实现(上)。在上篇中,已经完成语法规则、主要的数据结构设计。这里就继续完成程序,最后编译测试。
目录
回顾
这个系列我们需要通过flex/bison实现一个编译程序,能够实现一种简单的程序语言,这个程序语言包含了:基础运算、变量与赋值、表达式计算、
if语句、while语句、print语句等。例如,使用该程序语言,我们可以实现如下程序:i = 1; a = 0; while ( a < 100 ) { i = i + 1; a = a + i; } print i;该程序解决的问题是:在自然数序列(1、2、4…)中,前面多少个自然数的和首次大于100。你可以使用上面定义的语言,编写自己的程序。
好了,接着前面三篇的内容,我们继续完成该语言的编译程序。
主要的函数实现
build_node函数
t_node* build_node(enum NODETYPE nt,t_node* left,t_node* right, int i){ debug_print(__FILE__,__LINE__,__func__,""); t_node *t_n; t_n = NULL; t_n = (t_node *)malloc(sizeof(t_node)); if (t_n == NULL){ printf("Out of Memory\n"); exit(1); } t_n->nt = nt; t_n->left = left; t_n->right = right; t_n->i = i; return t_n; }exec_node函数
int exec_node(t_node *n){ if( n == NULL ) return 0; debug_print(__FILE__,__LINE__,__func__,"enter exec_node"); switch(n->nt){ case NT_INTEGER: debug_print(__FILE__,__LINE__,__func__,"NT_INTEGER node"); break; case NT_VAR_NAME: debug_print(__FILE__,__LINE__,__func__,"NT_VAR_NAME node"); break; case NT_O_ADD: exec_node(n->left); exec_node(n->right); n->i = get_node_ret(n->left) + get_node_ret(n->right); debug_print(__FILE__,__LINE__,__func__,"NT_O_ADD node"); break; case NT_O_MINUS: exec_node(n->left); exec_node(n->right); n->i = get_node_ret(n->left) - get_node_ret(n->right); debug_print(__FILE__,__LINE__,__func__,"NT_O_MINUS node"); break; case NT_O_MULTIPLY: debug_print(__FILE__,__LINE__,__func__,"NT_O_MULTIPLY node"); exec_node(n->left); exec_node(n->right); n->i = get_node_ret(n->left) * get_node_ret(n->right); break; case NT_BOOL_EXPR_GT: debug_print(__FILE__,__LINE__,__func__,"NT_BOOL_EXPR_GT node"); n->i = 0; exec_node(n->left); exec_node(n->right); if (get_node_ret(n->left) > get_node_ret(n->right) ) { n->i = 1 ; } break; case NT_BOOL_EXPR_LT: debug_print(__FILE__,__LINE__,__func__,"NT_BOOL_EXPR_LT node"); n->i = 0; exec_node(n->left); exec_node(n->right); if (get_node_ret(n->left) < get_node_ret(n->right) ) { n->i = 1 ; } break; case NT_IF: debug_print(__FILE__,__LINE__,__func__,"NT_IF node"); exec_node(n->left); if (get_node_ret(n->left)){ exec_node(n->right); } break; case NT_WHILE: debug_print(__FILE__,__LINE__,__func__,"NT_WHILE node"); exec_node(n->left); while( get_node_ret(n->left) ){ exec_node(n->right); exec_node(n->left); } break; case NT_PRINT: debug_print(__FILE__,__LINE__,__func__,"NT_PRINT node"); exec_node(n->left); printf("print '%d'",get_node_ret(n->left)); break; case NT_ASSIGNMENT: debug_print(__FILE__,__LINE__,__func__,"NT_ASSIGNMENT node"); exec_node(n->left); exec_node(n->right); var[n->left->i - 'a'] = get_node_ret(n->right); break; case NT_STATEMENT_BLOCK: debug_print(__FILE__,__LINE__,__func__,"NT_STATEMENT_BLOCK node"); exec_node(n->left); exec_node(n->right); break; case NT_PROGRAM: debug_print(__FILE__,__LINE__,__func__,"NT_PROGRAM node"); break; } return 0; }内存释放
int free_node(t_node *n){ if( n != NULL ) { free_node(n->left); free_node(n->right); } free(n); return 0; }工具函数debug_print
void debug_print(const char *fname, int lineno, const char *fxname, const char *debug_info){ #ifdef cal_DEBUG printf("\n debug: enter at line %d in %s,function: %s info: %s\n", lineno, fname, fxname, debug_info ); #endif }补充语法文件的Action部分
%% program: statement_block { exec_node($1); free_node($1); printf("\n job done! \n"); } ; statement_block: %empty { $$ = build_node( NT_STATEMENT_BLOCK, NULL, NULL, NULL, 0 ); debug_print(__FILE__,__LINE__,__func__,""); } | statement_block statement { $$ = build_node( NT_STATEMENT_BLOCK, $1, $2, NULL, 0 ); debug_print(__FILE__,__LINE__,__func__,""); } ; statement: assignment { $$ = $1; } | print_func { $$ = $1; } | if_block { $$ = $1; } | while_block { $$ = $1; } ; if_block: IF '(' bool_expr ')' '{' statement_block '}' { $$ = build_node( NT_IF, $3, $6, NULL, 0 ); } while_block: WHILE '(' bool_expr ')' '{' statement_block '}' { $$ = build_node( NT_WHILE, $3, $6, NULL, 0 ); } assignment: VAR_NAME '=' expression ';' { $$ = build_node( NT_ASSIGNMENT, build_node(NT_VAR_NAME,NULL,NULL,NULL,(int)$1), $3, NULL, 0); } print_func : PRINT expression ';' { $$ = build_node(NT_PRINT,$2,NULL,NULL,0); } bool_expr: expression GT expression { $$ = build_node(NT_BOOL_EXPR_GT,$1,$3,NULL,0);} | expression LT expression { $$ = build_node(NT_BOOL_EXPR_LT,$1,$3,NULL,0);} expression: INTEGER { $$ = build_node(NT_INTEGER,NULL,NULL,NULL,$1); } | VAR_NAME { $$ = build_node(NT_VAR_NAME,NULL,NULL,NULL,(int)$1); } | expression O_ADD expression { $$ = build_node(NT_O_ADD,$1,$3,NULL,0);} | expression O_MINUS expression { $$ = build_node(NT_O_MINUS,$1,$3,NULL,0);} | expression O_MULTIPLY expression { $$ = build_node(NT_O_MULTIPLY,$1,$3,NULL,0);} %%cal.header.h
enum NODETYPE{ NT_STATEMENT, NT_IF, NT_WHILE, NT_PROGRAM, NT_STATEMENT_BLOCK, NT_O_ADD, NT_O_MINUS, NT_O_MULTIPLY, NT_INTEGER, NT_VAR_NAME, NT_BOOL_EXPR_GT, NT_BOOL_EXPR_LT, NT_PRINT, NT_ASSIGNMENT }; typedef struct t_node{ enum NODETYPE nt; struct t_node* left; struct t_node* right; struct t_node* rrnode; int i; // for NT_INTEGER NT_VAR_NAME node }t_node;有了这些信息,就可以使用
NODETYPE来构建,解析树了。在每次解析到对应节点或进行Reduction时,我们在语法文件的Action部分就可以调用一个build_node函数来构建对应的节点。我们可以看看如下的程序的解析树结构:i = 1 ; a = 0 ; while ( a < 100 ) { a = a + i; i = i + 1; } print i ;这个程序,可以找到,在自然数级数中,到第几项的时候,其和就超过了100。
完整的代码
最后是程序实现的部分,包括
- build_node
- execute_node
- free_node
- get_node_ret
cal.l lex文件
cat cal.l %{ #include "cal.tab.h" %} %option noyywrap %% [[:digit:]]+ { yylval.a = atoi(yytext); return INTEGER; } [a-z] { yylval.c = yytext[0]; return VAR_NAME; } "+" { return O_ADD;}; "-" { return O_MINUS;}; "*" { return O_MULTIPLY;}; "while" {return WHILE;} "if" {return IF;} "print" {return PRINT;} ">" {return GT;} "<" {return LT;} [();={}] {return yylval.c = *yytext;} %%cal.header.h 头文件/数据结构定义
cat cal.header.h enum NODETYPE{ NT_STATEMENT, NT_IF, NT_WHILE, NT_PROGRAM, NT_STATEMENT_BLOCK, NT_O_ADD, NT_O_MINUS, NT_O_MULTIPLY, NT_INTEGER, NT_VAR_NAME, NT_BOOL_EXPR_GT, NT_BOOL_EXPR_LT, NT_PRINT, NT_ASSIGNMENT }; typedef struct t_node{ enum NODETYPE nt; struct t_node* left; struct t_node* right; struct t_node* rrnode; int i; // for NT_INTEGER NT_VAR_NAME node }t_node;cal.y 语言语法文件
cat cal.y %{ #include <stdio.h> #include <stdlib.h> #include "cal.tab.h" #include "cal.header.h" // #define cal_DEBUG 1 void debug_print(const char *fname, int lineno, const char *fxname, const char *debug_info){ #ifdef cal_DEBUG printf("\n debug: enter at line %d in %s,function: %s info: %s\n", lineno, fname, fxname, debug_info ); #endif } t_node* build_node(enum NODETYPE nt,t_node* left,t_node* right, t_node* r_right , int i){ debug_print(__FILE__,__LINE__,__func__,""); t_node *t_n; t_n = NULL; t_n = (t_node *)malloc(sizeof(t_node)); if (t_n == NULL){ printf("Out of Memory\n"); exit(1); } t_n->nt = nt; t_n->left = left; t_n->right = right; t_n->rrnode = r_right; t_n->i = i; return t_n; } int var[26]; int main (){ int yydebug=1; yyparse(); return 0; } void yyerror (char const *s) { fprintf (stderr, "something error: %s\n over", s); } int exec_node(t_node *n){ if( n == NULL ) return 0; debug_print(__FILE__,__LINE__,__func__,"enter exec_node"); switch(n->nt){ case NT_INTEGER: debug_print(__FILE__,__LINE__,__func__,"NT_INTEGER node"); break; case NT_VAR_NAME: debug_print(__FILE__,__LINE__,__func__,"NT_VAR_NAME node"); break; case NT_O_ADD: exec_node(n->left); exec_node(n->right); n->i = get_node_ret(n->left) + get_node_ret(n->right); debug_print(__FILE__,__LINE__,__func__,"NT_O_ADD node"); break; case NT_O_MINUS: exec_node(n->left); exec_node(n->right); n->i = get_node_ret(n->left) - get_node_ret(n->right); debug_print(__FILE__,__LINE__,__func__,"NT_O_MINUS node"); break; case NT_O_MULTIPLY: debug_print(__FILE__,__LINE__,__func__,"NT_O_MULTIPLY node"); exec_node(n->left); exec_node(n->right); n->i = get_node_ret(n->left) * get_node_ret(n->right); break; case NT_BOOL_EXPR_GT: debug_print(__FILE__,__LINE__,__func__,"NT_BOOL_EXPR_GT node"); n->i = 0; exec_node(n->left); exec_node(n->right); if (get_node_ret(n->left) > get_node_ret(n->right) ) { n->i = 1 ; } break; case NT_BOOL_EXPR_LT: debug_print(__FILE__,__LINE__,__func__,"NT_BOOL_EXPR_LT node"); n->i = 0; exec_node(n->left); exec_node(n->right); if (get_node_ret(n->left) < get_node_ret(n->right) ) { n->i = 1 ; } break; case NT_IF: debug_print(__FILE__,__LINE__,__func__,"NT_IF node"); exec_node(n->left); if (get_node_ret(n->left)){ exec_node(n->right); } break; case NT_WHILE: debug_print(__FILE__,__LINE__,__func__,"NT_WHILE node"); exec_node(n->left); while( get_node_ret(n->left) ){ exec_node(n->right); exec_node(n->left); } break; case NT_PRINT: debug_print(__FILE__,__LINE__,__func__,"NT_PRINT node"); exec_node(n->left); printf("print '%d'",get_node_ret(n->left)); break; case NT_ASSIGNMENT: debug_print(__FILE__,__LINE__,__func__,"NT_ASSIGNMENT node"); exec_node(n->left); exec_node(n->right); var[n->left->i - 'a'] = get_node_ret(n->right); break; case NT_STATEMENT_BLOCK: debug_print(__FILE__,__LINE__,__func__,"NT_STATEMENT_BLOCK node"); exec_node(n->left); exec_node(n->right); break; case NT_PROGRAM: debug_print(__FILE__,__LINE__,__func__,"NT_PROGRAM node"); break; } return 0; } int get_node_ret(t_node *n){ int r = n->i; switch(n->nt){ case NT_VAR_NAME: r = var[n->i - 'a']; break; } return r; } int free_node(t_node *n){ if(n != NULL){ // printf("\n try to free memory of node %d \n",n->nt); } return 0; } %} %union { int a; // for integer char c; // for var_name int int_bool; // for bool_expr struct t_node* t_n; } %type <t_n> expression bool_expr print_func assignment %type <t_n> while_block statement statement_block if_block %token <c> VAR_NAME %token <a> INTEGER %token O_ADD O_MINUS O_MULTIPLY %token GT LT %token WHILE IF %token PRINT %left O_ADD O_MINUS %left O_MULTIPLY %% program: statement_block { exec_node($1); free_node($1); printf("\n job done! \n"); } ; statement_block: %empty { $$ = build_node( NT_STATEMENT_BLOCK, NULL, NULL, NULL, 0 ); debug_print(__FILE__,__LINE__,__func__,""); } | statement_block statement { $$ = build_node( NT_STATEMENT_BLOCK, $1, $2, NULL, 0 ); debug_print(__FILE__,__LINE__,__func__,""); } ; statement: assignment { $$ = $1; } | print_func { $$ = $1; } | if_block { $$ = $1; } | while_block { $$ = $1; } ; if_block: IF '(' bool_expr ')' '{' statement_block '}' { $$ = build_node( NT_IF, $3, $6, NULL, 0 ); } while_block: WHILE '(' bool_expr ')' '{' statement_block '}' { $$ = build_node( NT_WHILE, $3, $6, NULL, 0 ); } assignment: VAR_NAME '=' expression ';' { $$ = build_node( NT_ASSIGNMENT, build_node(NT_VAR_NAME,NULL,NULL,NULL,(int)$1), $3, NULL, 0); } print_func : PRINT expression ';' { $$ = build_node(NT_PRINT,$2,NULL,NULL,0); } bool_expr: expression GT expression { $$ = build_node(NT_BOOL_EXPR_GT,$1,$3,NULL,0);} | expression LT expression { $$ = build_node(NT_BOOL_EXPR_LT,$1,$3,NULL,0);} expression: INTEGER { $$ = build_node(NT_INTEGER,NULL,NULL,NULL,$1); } | VAR_NAME { $$ = build_node(NT_VAR_NAME,NULL,NULL,NULL,(int)$1); } | expression O_ADD expression { $$ = build_node(NT_O_ADD,$1,$3,NULL,0);} | expression O_MINUS expression { $$ = build_node(NT_O_MINUS,$1,$3,NULL,0);} | expression O_MULTIPLY expression { $$ = build_node(NT_O_MULTIPLY,$1,$3,NULL,0);} %%编译与执行
lex cal.l && \ bison -d cal.y && \ gcc cal.tab.c lex.yy.c -o a.out && \ ./a.out < p.f.txt最后,需要注意,该程序更注重的是测试与实现,所以在“内存释放”可能会存在一些泄露的问题。
-
个人的一些脚本和代码,经常会分散在不同的地方,管理起来并不方便,例如给WordPress编写的Plugin、测试MySQL时使用的一些脚本等,所以打算全部使用GitHub管理起来。对于个人使用,GitHub提供了私人仓库以存储代码,可以较为方便的管理一些还没有公开的个人代码。
目录
建立个人Git和GitHub环境
GitHub CLI是一个具体简单交互式操作的命令行,可以完成与GitHub相关的一些交互与操作。对应的软件包/命令是
gh。安装gh-cli
参考:Installing gh on Linux and BSD。Amazon Linux 2上安装:
sudo yum-config-manager --add-repo https://cli.github.com/packages/rpm/gh-cli.repo sudo yum install gh使用gh配置GitHub授权
接着,就可以使用
gh auth login命令来进行GitHub的认证了(gh cli manual)。这是一个简单的交互式命令,这里使用https+token的方式完成认证(也可以使用浏览器的方式辅助完成命令行认证):gh auth login ? What account do you want to log into? GitHub.com ? What is your preferred protocol for Git operations on this host? HTTPS ? Authenticate Git with your GitHub credentials? Yes ? How would you like to authenticate GitHub CLI? Paste an authentication token Tip: you can generate a Personal Access Token here https://github.com/settings/tokens The minimum required scopes are 'repo', 'read:org', 'workflow'. ? Paste your authentication token: ********************************************************************************************* - gh config set -h github.com git_protocol https ✓ Configured git protocol ! Authentication credentials saved in plain text ✓ Logged in as orczhou关于Token的配置与获取,可以参考:GitHub->Settings->Developer Settings ,这里不再详述。注意,
Token意味着分配的所有的仓库权限,必须妥善保管,否则可能会带来巨大的安全隐患。如果要登出的话,则可以简单的使用如下命令:
gh auth logout在本地pull与push仓库
- 首先,在git中配置本地身份(用户名与)
git config --global user.name "orczhou" git config --global user.email "orczhou@orczhou"- 首先,新建一个本地模板,并使用git命令初始化
mkdir terraform && cd terraform git init- 配置远端(remote)分支;并拉取远端代码
git remote add origin https://github.com/orczhou/cloud-mysql-benchmark.git git pull origin main向远端push代码
这时,如果修改了仓库中的代码,则可以使用push命令向远端发起提交请求。
修改、测试并本地提交代码:
vi gcp_rds_ins/all_in_one/README.md git add gcp_rds_ins/all_in_one/README.md git commit -m "gcp readme updated"向远端push修改:
git push -u origin main该操作会向远端仓库的main分支,提交代码。
向main分之合并代码
可以在GitHub仓库页面,对比并创建一个pull request。
发起pr之后,代码仓库则可以进行merge操作,将代码合并到main分之。


在新增远程代码库(origin)
git remote add origin https://github.com/orczhou/testing-delete-repo-if-u-like.git将本地代码,提交到远程代码库(origin)的main分支:
git push -u origin main上面的,
-u origin main,其中-u参数表示push的目标代码库-u | --set-upstream在现有仓库的main分之上开发
经常需要做这个动作,常用的命令比较简单,这里记录如下:
mkdir repo_bench && cd repo_bench git init git branch -M main git remote add origin https://... git pull origin main直接修改本地
main中的代码并提交到源端:cat "..." > README.md git add README.md git commit -m "first commit" --dry-run git commit -m "first commit" git push -u origin main使用gitignore忽略文件
在代码开发过程中,由于编译、运行等原因会产生很多的中间文件,而这些中间文件时无需提交到代码仓库的。这时候,需要使用gitignore来忽略这部分文件。详细完整的gitignore的使用可以参考
man gitignore。常用的gitignore是可以通过.gitignore文件来实现,即在代码的根目录中新建该文件,那么在代码处理时,就会根据根据该文件的规则进行忽略。例如Terraform脚本常用的gitignore文件可以参考:
所以,一个Terraform脚本的.gitignore可以依次作参考:
# Compiled files *.tfstate *.tfstate.backup *.tfstate.lock.info # Directories .terraform/ .vagrant/ # SSH Keys *.pem # Ignored Terraform files *gitignore*.tfmaster分支与main分支
在搜索git/github的相关资料的时候,经常还会搜索到
master分支作为主分支的资料或者仓库。在2020年的George Floyd的案件发生后,美国的Black_Lives_Matter运动达到了前所未有的高度,最终也影响到在计算机领域的master/slave一词的使用。更多的参考:Renaming the default branch from master@GitHub、Why GitHub renamed its master branch to main@theserverside。不过,
git在本地默认还是使用默认的master分支,所以如果没有手动切换分支,则还是会经常“默认的”创建master分支。查看未提交的修改
git面向的场景就是分布式、多任务的开发代码管理,其独特的”three tree“模型可以很巧妙的实现这些能力。这也给初学者带来了很多理解上的障碍。

git diff与git diff HEAD如果,想要查看自上次
commit以来的所有变更,则需要试用git diff HEAD命令,通常HEAD指向的是,最后一次commit时的位置。# diff between "working" and "staging" git diff# diff between "working" and "repository" git diff HEAD# diff between "staging" and "repository" git diff --cached同步远程更新
个人代码仓库管理中,有时候会有这样的情况:直接在远程仓库中修改了一些文件,然后如何让本地和远程保持同步。考虑这样的场景:直接在GitHub上对
README.md文件进行了编辑,那么本地代码仓库如何保持这个更新。当然,这样做,通常可能会很危险:可能会覆盖掉你本地所做的更改,但是基于上面的场景,所以,有时候会需要这么做。Stackoverflow上有几个相关的问题,非常详细的介绍了做法:
- How do I pull files from remote without overwriting local files?
- What is the difference between ‘git pull’ and ‘git fetch’?
这里的推荐做法是这样,如果本地仓库的修改确定不要了(通常这是很危险的):
git pull如果本地仓库修改都还需要:
git stash git pull git stash pop还可以:
- 先使用
git fetch更新origin/main - 然后使用
git diff main origin/main查看本地与远程的差异 - 最后使用
git merge将origin/main与本地合并,并保持在本地
这样
origin/main是最新的,且本地分支也是最新的了git fetch git diff main origin/main git merge参考链接
-
引言
这是一个系列文章,旨在了解如何使用Flex/Lex和Yacc/Bison进行词法和语法解析。在前面,已经完成了使用Lex/flex做基础的词法解析、实现一个简单的计算器。开始这个系列的第三部分,使用flex和bison完成一个更加复杂的的编译程序。整体上有一定的复杂度,所以分上下篇分别介绍。上篇介绍:实现概要、语法、,下篇介绍数据结构与实现(包括解析树实现、执行)。
- 下篇介绍:数据结构与实现
- 与Grammar对应的“解析树”
- “解析树”的执行
- 解析树的节点,Terminals 和 Non-Terminals
- 一个“简单语句”的解析树结构
- 一个“略微复杂语句”的解析树结构
解析与执行包含赋值、IF、WHILE等语句的程序
在前面的案例中,我们实现了一个简单的加减乘运算的计算器程序。这里我们尝试实现一个更复杂一些的编译程序,语法能够支持如下内容:
- 包含了变量,可以对变量赋值,也可以在表达式中使用变量。但是为了简化程序,这里变量仅限于使用单个小写字母,即[a-z]
- 支持条件运算,这里定义简单的语法如下:if ( expr ) expre ; (忽略了else语法)
- 支持比较运算符,包括大于、小于
- 支持循环运算,支持while循环
- 和前面案例的一样,仅处理整数,故不处理除法,也不考虑整数溢出等边界问题
我们用这几个能力,可以编写如下的程序:
i = 1; a = 0; while ( a < 100 ) { i = i + 1; a = a + i; } print i;这个程序实现了一个简单的功能,解决的问题是:在自然数序列(1、2、4…)中,前面多少个自然数的和首次大于100。你可以使用上面的命令编写其他的任意程序。
解析树的节点
如果只是使用前面的指令,似乎难以实现对if/while语句的支持。这里,就需要使用典型的编译与执行思路了,先使用语法解析构建一个“语法树”(也叫“解析树”),然后再执行该解析树。具体的,一些设计如下:
- 使用一个全局数组(
int * var[26])存储变量,因为在前面限制了变量名只能是[a-z] - 每个grammar rule对应一个tree node,并依次构建一棵语法树
- 语法树的节点设计如下:
typedef struct t_node{ enum NODETYPE nt; struct t_node* left; struct t_node* right; int i; // for NT_INTEGER NT_VAR_NAME node }t_node;这里为了简化:
- 所有语法节点都存放在该结构中
- 对于变量名,本应该是一个字符,这里在存储时,直接使用其ASCII码将其存储为整数
语法规则设计
在开始实现与执行解析树之前,我们先定义语法规则,以支持赋值、if、while、print等语法。语法规则定义时,需要注意尽量避免出现shift/reduce冲突,并且这里的语法规则不包含Action部分:
//start symbol program: | program statement_block { printf("\n job done! \n"); } ; statement_block: | statement_block statement ; statement: assignment | print_func | if_block | while_block ; while_block: WHILE '(' bool_expr ')' '{' statement_block '}' if_block: IF '(' bool_expr ')' '{' statement_block '}' assignment: VAR_NAME '=' expression ';' print_func : PRINT expression ';' | PRINT VAR_NAME ';' bool_expr: expression GT expression | expression LT expression expression: INTEGER | VAR_NAME | expression O_ADD expression | expression O_MINUS expression | expression O_MULTIPLY expression节点分析
节点分析:INTEGER VAR_NAME
- 节点类型(enum NODETYPE)分别是:NT_INTEGER NT_VAR_NAME
- 没有子节点,故left/right node都是NULL
- 在初始化时,
- 对于INTEGER:
t_node.a存储的是具体的整数 - 对于VAR_NAME:则存储的变量名,这了变量名为[a-z],则将其ASCII存放于
t_node.a
- 对于INTEGER:
节点分析:expression 与 O_ADD O_MINUS O_MULTIPLY
expression对应的语法规则如下
expression: INTEGER | VAR_NAME | expression O_ADD expression | expression O_MINUS expression | expression O_MULTIPLY expression那么,再看看O_ADD O_MINUS O_MULTIPLY这类节点:
- NODETYPE 分别是 NT_O_ADD NT_O_MINUS NT_O_MULTIPLY
- 都有两个子节点,left / right
- 在execute之后存储,各个expression计算的结果值
t_node.a中 - 注意,在这个设计中,无需有一个独立的expression节点
bool表达式(bool_expr),通常用于条件判断
bool_expr: expression GT expression | expression LT expression- 节点类型(enum NODETYPE)为
NT_BOOL_EXPR - 有两个子节点,执行该节点时,需要执行两个子节点之后,获得两个子节点的结果值,再进行比较
- 返回值为为bool型,这里使用int存储,0表示FALSE 1表示TRUE
print_func节点
这个节点实现一个打印整数值的功能,参数可以是一个变量,也可以是一个表达式:
print_func : PRINT expression ';' | PRINT VAR_NAME ';'NODETYPE为NT_PRINT_FUNC- 只有一个子节点,为一个 expression 或 VAR_NAME (注:这里应该只用expression就可以了,因为VAR_NAME也是expression)
- 执行该节点时,则需要实际调用一次打印函数,向标准输出打印
expression的结果值
assignment 赋值语句
assignment: VAR_NAME '=' expression ';'赋值语句左边是变量名,这里定义是[a-z],右边是一个表达式,语句以分号结束。
- 其节点类型(NODETYPE)为:NT_ASSIGNMENT
- 左子节点为
VAR_NAME,右子节点为expression - 其执行时,需要将expression的结果值,存储到变量数组对应的整型变量中
while_block WHILE子句
while_block: WHILE '(' bool_expr ')' '{' statement_block '}'- 其节点类型(NODETYPE)为:NT_WHILE
- 左子节点为:
bool_expr右子节点为 :statement_block - 执行该节点时,也是也while循环执行,条件部分是执行并判断
bool_expr的真假,再决定是否执行右子节点。这里需要注意,每次获取bool_expr的时候,都需要先执行一次该节点。
if_block IF子句
if_block: IF '(' bool_expr ')' '{' statement_block '}'- 其节点类型(NODETYPE)为:NT_IF
- 左子结点为 bool_expr 右子节点 : statement_block
- 执行时,先执行左子节点,再获取其结果的真/假,再判断是否执行右子节点
statement
statement: assignment | print_func | if_block | while_block可以看到,statement由assignment、print_func、if_block、while_block是这些中的任何一个。所以,在实际构建中,并不会有该节点。与expression类似。
statement_block 多个statement
statement_block: | statement_block statement ;- 其节点类型(NODETYPE)为:NT_STATEMENT_BLOCK
- 左子节点为 : statement_block,即为statement_block或者assignment、print_func、if_block、while_block中的任意一个;右子节点为 : assignment、print_func、if_block、while_block
- 执行时,先执行左子节点,再执行右子节点
program
program: | program statement_block { printf("\n job done! \n"); } ;主要的数据结构与函数
build_node:构建当前语法规则的节点,该函数返回当前构建出来节点的指针,通常也是各个语法规则Action部分的返回值。
t_node* build_node( enum NODETYPE nt, t_node* left, t_node* right, int i)exec_node:执行某个节点,并执行其左/右子节点(如果存在的话)。不同的节点的执行操作会有一些不同,例如:if节点需要做一些判断,再决定是否执行;while节点则需要循环bool_expr以决定是否执行某段代码。- 加法节点,则需要执行左右子节点执行结果,并相加
各类节点的exec操作可以参考上一节的详细描述。该函数定义如下
int exec_node(t_node *n)- 解析树的节点与节点类型
typedef struct t_node{ enum NODETYPE nt; struct t_node* left; struct t_node* right; struct t_node* rrnode; int i; // for NT_INTEGER NT_VAR_NAME node }t_node;enum NODETYPE{ NT_STATEMENT, NT_IF, NT_WHILE, NT_PROGRAM, NT_STATEMENT_BLOCK, NT_O_ADD, NT_O_MINUS, NT_O_MULTIPLY, NT_INTEGER, NT_VAR_NAME, NT_BOOL_EXPR_GT, NT_BOOL_EXPR_LT, NT_PRINT, NT_ASSIGNMENT };下一篇,我们将基于此完成完整的代码。
不包含Action代码的语法
补充完整的语法文件cal.y
包括:
- 入口函数
- NODETYPE 定义
- 解析树的节点 t_node
- 声明节点便利函数 exec_node
- 用于存储变量的数组 int var[26];
- YYSTYPE (似乎是不需要)
- 定义lex需要处理的TOKEN
- 定义运算符优先级、结合律
%{ // 入口函数 #include <stdio.h> int main (){ yyparse(); return 0; } enum NODETYPE{ NT_PROGRAM, NT_STATEMENT_BLOCK, NT_STATEMENT, NT_IF, NT_WHILE, NT_O_ADD, NT_O_MINUS, NT_O_MULTIPLY, NT_INTEGER, NT_VAR_NAME }; typedef struct t_node{ enum NODETYPEP nt; t_node * left; t_node * right; t_node * rrnode; YYSTYPE yval; }t_node; //递归执行整个parser tree int exec_node(t_node *n){ return 0; } int var[26]; %} // 定义YYSTYPE %union { int a; // for integer char c; // for var_name bool b; // for bool_expr } // 定义Token %token <c> VAR_NAME %token <a> INTEGER %token O_ADD O_MINUS O_MULTIPLY %token GT LT %token WHILE IF %token PRINT // 定义运算符 %left O_ADD O_MINUS %left O_MULTIPLY补充Lex文件
%{ #include "cal.tab.h" %} %option noyywrap %% [[:digit:]]+ { yylval.a = atoi(yytext); return INTEGER; } [a-z] { yylval.c = yytext[0]; return VAR_NAME; } "+" { return O_ADD;}; "-" { return O_MINUS;}; "*" { return O_MULTIPLY;}; "while" {return WHILE;} "if" {return IF;} "print" {return PRINT;} ">" {return GT;} "<" {return LT;} [();={}] {return yylval.c = *yytext;} %%生成代码并编译、修改
lex cal.l bison -d cal.y gcc cal.tab.c lex.yy.c -o a.out bison -W -d cal.y cal.y:62.8: warning: empty rule without %empty [-Wempty-rule] program: ^ cal.y:66.16: warning: empty rule without %empty [-Wempty-rule] statement_block: ^ cal.y: warning: 6 shift/reduce conflicts [-Wconflicts-sr]错误1:这里的 PRINT expression ‘;’
PRINT VAR_NAME ‘;’是有包含关系,重复的。因为VAR_NAME本身也是一个expression。故修改如下:print_func : PRINT expression ';'| PRINT VAR_NAME ';'expression: INTEGER | VAR_NAME | expression O_ADD expression | expression O_MINUS expression | expression O_MULTIPLY expression疑问与思考:左边/右边的表达有什么不同。(注意,左边的表达会报shift/reduce conflict)
program: | program statement_block ; statement_block: | statement_block statement ; statement: assignment | print_func | if_block | while_block ;program: statement_block ; statement_block: | statement_block statement ; statement: assignment | print_func | if_block | while_block ;修改后的cal.y文件,仅语法部分,不包含Action
%% program: statement_block { printf("\n job done! \n");} ; statement_block: | statement_block statement ; statement: assignment | print_func | if_block | while_block ; if_block: IF '(' bool_expr ')' '{' statement_block '}' while_block: WHILE '(' bool_expr ')' '{' statement_block '}' assignment: VAR_NAME '=' expression ';' print_func : PRINT expression ';' bool_expr: expression GT expression | expression LT expression expression: INTEGER | VAR_NAME | expression O_ADD expression | expression O_MINUS expression | expression O_MULTIPLY expression - 下篇介绍:数据结构与实现
-
这是一个跨越数年的系列,历史文章参考:
概述
Gartner云数据库魔力象限(参考,后面简称MQ)一直是全球数据库领域的年度”锦标赛”,其目标是帮助企业技术决策者选择更加适合的数据库厂商。也因为Gartner的专业与专注,其榜单在各个领域都被众多的企业技术决策者所认可。所以,数据库领域的MQ也就成为了各大厂商年度一场重要的”考试”。放榜了:

中国数据库厂商:阿里云孤军奋战,虽艰难,但依旧在向前
Gartner的云数据库魔力象限是一个全球的比较,关注的也是各个厂商在全球是市场的表现。所以,即便中国厂商在中国这个单一市场上做得很好,也无法受到认可。
在今年的魔力象限中,中国厂商,只剩下阿里云数据库在孤军奋战了。在诸如SAP、IBM、Teradata等一众厂商均跌出领导者象限的背景下,阿里云数据库不仅稳稳的被评为全球领导者,而且其位置相比于去年,无论是横纵坐标均有增强,这是非常了不起的。

另一方面,也可以注意到,跌出领导者象限的厂商,其对应的云战略通常也是相对失败的,这则是“云计算”是如何重塑数据库领域的具体表现。
比较遗憾的是,在继去年华为退出魔力象限后,注意到今年腾讯云也退出了该魔力象限。
在“象限”报告最后的“Honorable Mentions”部分,还提到的中国厂商包括OceanBase、PingCAP/TiDB(总部在Sunnyvale CA,姑且也算是中国厂商吧)、华为云、腾讯云。其中,OceanBase在中国分布式数据库市场有着非常强的表现,包括产品能力、性能以及市场占有率。
新增分布式关系型数据库厂商
今年,象限中新增了一个独立的分布式关系型数据库厂商:Yugabyte。在这块市场上,国内厂商包括TiDB、OceanBase,海外有CockroachDB(依旧在象限中),加之今年Aurora Limitless,可以预见这块并不宽敞的赛道上,未来的竞争会更加激烈。换个角度来看,也说明,随着数字化的更加深入,这个赛道似乎有一些转暖的迹象。
也非常期待,未来TiDB、OceanBase、PolarDB-X、TDSQL、openGauss等能够在全球市场一展拳脚。
领跑组:Amazon、微软、Oracle、Google
分析了Garnter十年的魔力象限,从2013年2016年,Oracle和Microsoft一直是“绝对第一梯队”,并保持着统治性的领先。自2017年,Amazon开始进入“绝对第一梯队”,2021年开始Google进入该“梯队”,Oracle虽然依旧保持在梯队中,但位置已经开始有一些相对落后。
在过去十年中,一直保持在“绝对第一梯队”就是“微软”,实属不易。微软是一家“巨无霸”公司,但是过去十年从企业软件,到云计算,再到AI争夺战,微软都是其中的“弄潮儿”。在数据库领域,微软有老牌数据库SQL Server,云上是SQL Database;还是自研的多模数据库Cosmos DB,以及托管数据库系列Azure Database。

Oracle依旧拥有这最强的数据库产品Oracle数据库,以及最流行的开源数据库MySQL。但受限于其在云计算市场现状,很多企业在选择数据库时,会同时考虑云平台的集成度,这也让Oracle感受到了巨大的危机。
Google是云计算的“后来居上者”。Google通过开放的策略,在云计算领域在逐步赶超对手。他的“开放”包括:面相生态的开放;以及面向其他所有云的开放。例如,Google云提供的一方托管数据库产品种类应该是最少的,Google鼓励用户使用第三方厂商基于Google云构建的服务(例如MongoDB等),而不是全部都由Google一方提供;再比如Google通过BigLake服务很好的兼容不同的云平台与环境,无论企业的数据在哪里,Google都提供了非常好兼容性,最终帮助用户使用Google云平台的分析服务构建自己的Lakehouse。
其他
- TigerGraph 去年昙花一现后,今年也退出了魔力象限。
- 在今年,IBM/SAP/Teradata/Cloudera“组团”跌入远见者象限,如果从更长时间的尺度去看,每个时代都有每个时代都有时代的特点,在“云计算”时代,没能够踩对节奏,就会在市场上遇到挑战。如果未来十年是AI的时代,谁会成为新的弄潮儿,也让人期待。
- MongoDB、Snowflake、DataBricks位置较为稳定,Snowflake在纵坐标的相对位置有所下降,这可能是其市场占有率被更多厂商在挑战的表现。
- EDB再次回到“象限”中,EDB提供基于PostgreSQL的Oracle兼容版,同时提供增强的PostgreSQL版本。随着,最近几年PostgreSQL的回暖
过去十年对比参考

2023 Gartner 云数据库魔力象限 
2022 
2021 
2020 
2019 
2018 
2017 
2016 
2015 
2014 
2013 Gartner数据库魔力象限十年对比
关于Gartner云数据库魔力象限
Gartner 云数据库魔力象限,其英文全称为:Magic Quadrant™ for Cloud Database Management Systems。该报告的核心是其魔力象限图。该图有横纵两个坐标,横坐标是”COMPLETENESS OF VISION”,代表了厂商的”远见/软实力”,或者说是“对领域未来理解判断”,具体的包括市场理解、产品策略、创新能力、商业模式等的理解和策略等。纵坐标是“ABILITY TO EXECUTE”,代表了厂商的“硬实力”,包括产品和服务能力、销售定价、市场响应、客户服务等能力。
- 如果横纵坐标”双高”(软硬皆强)那么就是在第一象限,也就是“领导者
- “如果”软实例”很强,则会落在第四象限,被称为”VISIONARIES”,译为”远见者”
- 如果”硬实力”很强,则会落在第二象限,被称为”CHALLENGERS”,译为”挑战者”
- 如果”软硬”都相对不算强(注意,这里是”相对”,因为进入了该象限都已经是全球范围内都有竞争力的选手了),那么则落在第三象限,被称为”NICHE PLAYERS”,译作”特定领域者”,这个翻译不是很好理解,其意思有两方面一个是,在某个特定的领域非常强,另外,就是,软实力和硬实力都还相对不算强。不用太纠结翻译
更多说明可以参考:数据库魔力象限2022:阿里领先、腾讯再次进入、华为退出文章中的说明。
最后
普通用户可以通过,各个数据库厂商对外公开的页面下载最新的“Gartner云数据库魔力象限”。完整的文档中,包含了更多关于各个厂商优势和缺点的描述,可以作为数据库厂商选型的重要参考。