admin

  • 引言

    这是一个系列文章,旨在了解如何使用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

    节点分析: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 ';' 
    • NODETYPENT_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的回暖

    过去十年对比参考

    Gartner数据库魔力象限十年对比

    关于Gartner云数据库魔力象限

    Gartner 云数据库魔力象限,其英文全称为:Magic Quadrant™ for Cloud Database Management Systems。该报告的核心是其魔力象限图。该图有横纵两个坐标,横坐标是”COMPLETENESS OF VISION”,代表了厂商的”远见/软实力”,或者说是“对领域未来理解判断”,具体的包括市场理解、产品策略、创新能力、商业模式等的理解和策略等。纵坐标是“ABILITY TO EXECUTE”,代表了厂商的“硬实力”,包括产品和服务能力、销售定价、市场响应、客户服务等能力。

    • 如果横纵坐标”双高”(软硬皆强)那么就是在第一象限,也就是“领导者
    • “如果”软实例”很强,则会落在第四象限,被称为”VISIONARIES”,译为”远见者”
    • 如果”硬实力”很强,则会落在第二象限,被称为”CHALLENGERS”,译为”挑战者”
    • 如果”软硬”都相对不算强(注意,这里是”相对”,因为进入了该象限都已经是全球范围内都有竞争力的选手了),那么则落在第三象限,被称为”NICHE PLAYERS”,译作”特定领域者”,这个翻译不是很好理解,其意思有两方面一个是,在某个特定的领域非常强,另外,就是,软实力和硬实力都还相对不算强。不用太纠结翻译

    更多说明可以参考:数据库魔力象限2022:阿里领先、腾讯再次进入、华为退出文章中的说明。

    最后

    普通用户可以通过,各个数据库厂商对外公开的页面下载最新的“Gartner云数据库魔力象限”。完整的文档中,包含了更多关于各个厂商优势和缺点的描述,可以作为数据库厂商选型的重要参考。

  • 测试说明

    这是一个云数据库性能测试系列,旨在通过简单标准的性能测试,帮助开发者、企业了解云数据库的性能,以选择适合的规格与类型。这个系列还包括:

    云数据库(RDS MySQL)性能深度测评与对比

    阿里云RDS标准版(x86) vs 经济版(ARM)性能对比

    华为云RDS通用型(x86) vs 鲲鹏(ARM)架构的性能对比

    AWS基于x86 vs Graviton(ARM)的RDS MySQL性能对比(二)

    AWS基于x86 vs Graviton(ARM)的RDS MySQL性能对比

    阿里云RDS存储类型概述

    阿里云RDS提供了较为丰富的存储类型选择,包括ESSD PL0、ESSD PL1、ESSD PL2、ESSD PL3、通用云盘、本地SSD。其中ESSD PL0仅在非常小的经济型规格中提供,并不在测试范围内。根据阿里云的官方文档可以看到,从PL0到PL3,性能越来越强,并且IO能力也越来越稳定(详细参考:ESSD云盘@阿里云文档中心),当然价格也越来越贵。这里摘抄了文档中的描述,以及关键的部分,对比如下:

    此外,“通用云盘”的说明可以参考阿里云文档描述:“通用云盘兼容ESSD云盘的所有特性,性能与ESSD PL1云盘相同,在ESSD云盘的基础上提供了IO突发能力。” 所以,可以这样理解,通用云盘是一种具备ESSD PL1能力,同时具备更加灵活的IOPS突发增长能力的云盘。突发IOPS部分,将额外计费(有少部分的免费额度),突发IOPS部分的额外费用为:0.02元/万IO。

    不同存储类型的性能趋势对比

    那么,我们看看在RDS MySQL中这些不同的存储的性能表现。这里依旧选择了“企业级规格”进行比较(企业级规格的定义可以参考:云数据库(RDS MySQL)性能深度测评与对比),详细的性能如下:

    可以看到:

    • 对于几种云盘的存储,RDS表现出了较为一致的性能,即,使用更好的存储的RDS总是能够获得了更好的性能:ESSD PL3 > ESSD PL2 > 通用存储 >> ESSD PL1
    • 本地SSD,性能则是最差的存储,相较于ESSD PL1要低9%;相较于性能最好的ESSD PL3则要低18.7%
    • 不过,相较于ESSD PL1/2/3之间成倍的价格增长,从这里的测试性能并没有展示出那么大的差距
    (more…)
  • 一直都在非常深度调研、关注和使用云数据库,其中性能是关注的重点之一。一方面性能是最终成本的重要影响因素,更好的性能,通常意味着使用更少的资源支撑更高的业务量,从而降低整体成本。另外,性能还意味着在极端场景下,数据库的上限支撑能力。

    所以,近期对各个云数据库厂商做了一个较为系统的性能对比,供开发者和企业在云数据库选型时的参考。

    在进行大量测试之后,对主要的云厂商分别选择了一个“企业级规格”(适合生产环境配置的)进行了对比。先看性能对比如下图:

    可以看到:

    • 华为云数据库(红色),在中高并发度时(>=16),性能最强,且高于第二名约18%;
    • 腾讯云数据库(紫色),在低并发时(<=8),性能最强,高于第二名约15%;
    • 百度云数据库(淡蓝色),在中高并发时(>16),性能跃居第二,仅次于华为云;
    • 阿里云数据库(黄色)和谷歌云数据库(绿色)在低并发时也都有不错的表现,高并发之后性能则持续稳定在850 tps。这两家云数据库的响应延迟,表现得也非常接近。
    • 亚马逊云数据库(Amazon RDS,蓝色)和微软Azure云在中低并发时,表现出了较为相近的性能趋势,且性能较低。但是在高并发时,两者都表现出了非常强的扩展性,AWS RDS在96和128并发时,性能超过阿里云、腾讯云、谷歌云等,跃居第三;同样的,微软Azure云的数据库在高并发时也超过了阿里云、谷歌云,也有不错的性能表现。
    (more…)
  • TL;DR:用8.0系列中的8.0.34之后的版本,该系列版本后续主要以保障稳定、修复bug为主。MySQL 8.1、8.2都是“创新版”,很长时间都会快速迭代,稳定性要差一些,而且目前还不确定会添加哪些新功能在里面。9.0官方预计1年后就会发布,不过不重要,也会先发布Innovation版。

    MySQL在今年7月正式引入了新的版本发布模式,引入了包括8.1、8.2等版本。新的版本,给MySQL的新特性开发带了很大的好处,也让开发者容易变得困惑。这里概述一下各个版本,以及后续的版本发布规划,帮助开发者们在生产环境选择自己合适的版本。

    • 从MySQL 8.0.34开始,8.0系列将以Bugfix为主,保障稳定,是当前的LTS(Long Term Support)版本,一直到EOL(约为2026年4月)
    • 8.1/8.2版本,当前都是Innovation版本,预计在一年后,发布新的LTS版本;8.1 / 8.2 版本目前来看,还没有什么特别的功能引入,期待后续迭代
    • 对于LTS版本,其生命周期是标准的5+3年,5年“完整支持”、3年“扩展支持”
    • 预计8.4会是下一个LTS版本,在下一个LTS版本发布的时候,9.0就会正式发布,所以,9.0版本可能会在一年后就发布。届时,8.4就是8.x的LTS版本,同时发布9.0版本(Innovation版)。
    • Innovation版本,可以理解为,MySQL的官方工程师们可以“大刀阔斧”的做一些修改,除了添加新功能外,还可能删除某个功能、重构某个功能等。
    • MySQL 8.0的Premier Support将会到2025年4月;Extended Support 会到2026年4月

    参考:

  • 这是一个云数据库性能的系列文章,包含了:

    在前篇中(参考),较为详细的对比x86和Graviton 2(AWS推出第二代ARM芯片)的性能。Graviton 3实例在今年的4月份支持了RDS数据库(参考)。这里,我们再系统的看看m7g实例(Graviton 3)、m6g实例(Graviton 2)、m5实例(Intel Xeon)、m6i实例(第三代Intel Xeon/Ice Lake)的RDS性能(包括性价比)表现如何。

    测试结论

    参考下图。整体上,在中低并发时,m6i实例(第三代Intel Xeon/Ice Lake)、m5实例(Intel Xeon)性能要比m7g实例(Graviton 3)、m6i实例(第三代Intel Xeon/Ice Lake)实例要略微高一些。即,低并发时,x86实例性能要高出约10%

    在超高并发的时候性能表现:m7g实例 > m6i实例 > m6g实例 ~ m5实例。在超高并发下,m7g、m6i实例表现出了非常强的扩展性和吞吐量,m7g实例吞吐量最高,甚至高出m6i实例10%;相比m6g、m5实例,m7g实例性能则要高出30%

    为了更加直观对比性价比,这里选取了16并发的性能进行对比。m7g实例在16并发下,tps为314,价格为$0.936/小时;m6i实例的tps为336,价格为$0.94/小时。所以,m6i实例(x86)性价比要比m7g实例(Graviton 3)更高,高出约:6%。在超高并发时(128并发),m7g实例(Graviton 3)实例性价比才比m6i实例(x86)要更高,高出约:10%。不过,无论怎样,这与AWS宣称的Graviton实例性价比更高的结论是不一致的。

    测试模型说明

    这里使用了sysbench的读写混合模型(oltp_read_write)进行测试,单表大小为100万,共十个表,单次测试时长为300秒,分别测试了如下的并发度的性能表现:2 4 8 16 24 32 48 64 96 128

    实例配置与价格

    这里关注db.m7g.xlarge、db.m6i.xlarge实例价格,m6g、m5实例的价格可以参考前篇。以东京地区、多可用区实例价格为参考:

    实例配置与之前的测试保持一致。选择了较为常用4c16gb的实例进行测试,各个选项尽量选择默认选项,以更加接近的模拟用户实际场景,具体的,版本是AWS多可用区版、存储默认加密、gp3存储、100GB空间、3000 IOPS、Performance Insight也默认开启。

    详细的测试数据参考

    AWS RDS Graviton 3(db.m7g.xlarge/gp3/100gb/3000iops)

    threads|transactions| queries| time |avg/Latency|95%/Latency
    2|       10847|  216940|300.04|      55.32|     127.81
    4|       25897|  517940|300.00|      46.34|      51.94
    8|       49010|  980200|300.05|      48.97|      55.82
    16|       94335| 1886700|300.05|      50.89|      58.92
    24|      141987| 2839740|300.05|      50.71|      59.99
    32|      185996| 3719920|300.05|      51.62|      62.19
    48|      268264| 5365280|300.05|      53.68|      68.05
    64|      341468| 6829360|300.06|      56.23|      74.46
    96|      446113| 8922260|300.06|      64.56|      92.42
    128|      491663| 9833260|300.09|      78.11|     121.0

    AWS RDS x86(第三代Intel Xeon/Ice Lake)(db.m6i.xlarge/gp3/100gb/3000iops)

    threads|transactions| queries| time |avg/Latency|95%/Latency
    2|       13281|  265620|300.02|      45.18|     112.67
    4|       29635|  592700|300.04|      40.49|      47.47
    8|       53875| 1077500|300.04|      44.55|      53.85
    16|      100785| 2015700|300.07|      47.63|      57.87
    24|      150515| 3010300|300.04|      47.84|      59.99
    32|      193195| 3863900|300.05|      49.69|      63.32
    48|      273454| 5469080|300.08|      52.67|      69.29
    64|      343939| 6878780|300.05|      55.83|      75.82
    96|      408551| 8171020|300.09|      70.50|      99.33
    128|      438708| 8774160|300.06|      87.54|     123.28

    小结

    经过较为详细的测试,可以看到,在RDS数据库的场景下,无论是第二代自研芯片Graviton2,还是第三代Graviton3,相比于x86芯片在性价比上并没有特别明显的优势。而在更加常见的低并发的场景下,x86实例的性价比依旧是更高的。在超高并发时,Graviton3实例虽然表现出了一些性价比优势,但是,如此高的并发,其实在实际应用中,并不常见。

    另外,第三代Graviton3相比第二代Graviton2的性能提升也是非常明显,大概有10~40%的性能提升

    当然,这应该也是符合预期的结论,毕竟在大原则上,处理复杂的负载x86芯片应该更有优势;对简单的场景、更低功耗的场景,Graviton(ARM)芯片是更有优势的。对于数据库来说,涉及到事务处理、磁盘IO、大量的比较判断等,还是比较复杂的。不过,依旧期待未来,Graviton做更多的适配与正对性的优化,以获得更高的性价比,从而降低最终降低使用RDS的成本。

    参考