IT牛人博客聚合网站 发现IT技术最优秀的内容, 寻找IT技术的价值 http://www.udpwork.com/ zh_CN http://www.udpwork.com/about hourly 1 Mon, 27 Jun 2022 11:33:31 +0800 <![CDATA[什么是分布式一致性]]> http://www.udpwork.com/item/18147.html http://www.udpwork.com/item/18147.html#reviews Sun, 05 Sep 2021 10:49:53 +0800 ideawu http://www.udpwork.com/item/18147.html 在工程实践上, 分布式一致性和多副本有关系, 如果没有多副本, 就没有分布式一致性的问题.

多副本的定义: 多副本可以放在多台机器上, 也可以放在同一个进程内的不同内存地址内, 或者一个副本在内存, 一个副本在硬盘. 只要同一个对象出现在多处, 或者在多处被引用, 就是多副本.

各个副本的写入操作序列必须先经过共识, 按同样的顺序写入, 因此所有副本的状态将是最终一致的(相同). 但是, 有可能单独地读取某个副本, 这就导致读操作在不同副本上发生的顺序并不相同, 这显然会导致最终结果不一致(符合预期), 因为我们本能地知道, 顺序决定结果.

例如, 先写后读与先读后写, 显然读出来的结果不一样, 这个很显然 . 因为日志序列的复制和执行必然是异步的, 绝对不可能所有副本在同一个时间点同时写入, 必然有一个时间差, 这也是很显然 的. 因此, 如果轻率地去读取不同副本, 将可能导致读取的结果不同, 因为某个写入操作可能只在某个副本上执行了, 而在另一个副本上还没有执行, 所以读取的结果不同, 这是很显然 的.

所以, 分布式一致性的本质在这里就显而易见了 - 那就是让操作在某个副本上发生的顺序符合我们的期望 .

例如, 我们可能读取副本A, 也可能读取副本B, 或者同时读取副本A和B, 如果我们让所有的操作, 包括读和写在两个副本上发生的顺序相同, 那么, 结果必然是一致的, 这是很显然的.

这也是为什么有一些系统, 会把读请求放入 Raft 日志序列中进行同步的原因, 因为不同副本上的 Raft 的日志序列是相同排序的. 一条 Raft 日志序列是全序的 , 但不相关的操作之间可能没有相互依赖和影响, 所以全序没有必要. 例如, 某些日志操作对象 a, 某些日志操作对象 b, 这两类操作之间没有必要排序, 让它们单独排序就可以了, 即所谓的偏序 .

简单的实现方法就是做 Sharding, 把针对不同对象的操作拆分到不同的日志序列中, 也即所谓的 Multi Raft Group.

还有一种实现是不把读操作放到日志序列中, 而是通过停止等待, 等待某一个写操作 在某个副本发生之后, 再执行读操作, 便保证了顺序. 这就是 Read Index 的本质, Read Index 先找出全部副本的最新的共识, 然后等最新的那条共识执行之后, 再执行读操作, 也就必然保证读操作发生在我们期望的那一次写操作 之后.

Related posts:

  1. Paxos和Raft读优化 – Quorum Read 和 Read Index
  2. Raft 为什么不能直接 commit 前任的日志?
  3. Paxos 与分布式强一致性
  4. 什么是 Paxos 的日志空洞?
  5. 分布式存储名词解析 – 一致性
]]>
在工程实践上, 分布式一致性和多副本有关系, 如果没有多副本, 就没有分布式一致性的问题.

多副本的定义: 多副本可以放在多台机器上, 也可以放在同一个进程内的不同内存地址内, 或者一个副本在内存, 一个副本在硬盘. 只要同一个对象出现在多处, 或者在多处被引用, 就是多副本.

各个副本的写入操作序列必须先经过共识, 按同样的顺序写入, 因此所有副本的状态将是最终一致的(相同). 但是, 有可能单独地读取某个副本, 这就导致读操作在不同副本上发生的顺序并不相同, 这显然会导致最终结果不一致(符合预期), 因为我们本能地知道, 顺序决定结果.

例如, 先写后读与先读后写, 显然读出来的结果不一样, 这个很显然 . 因为日志序列的复制和执行必然是异步的, 绝对不可能所有副本在同一个时间点同时写入, 必然有一个时间差, 这也是很显然 的. 因此, 如果轻率地去读取不同副本, 将可能导致读取的结果不同, 因为某个写入操作可能只在某个副本上执行了, 而在另一个副本上还没有执行, 所以读取的结果不同, 这是很显然 的.

所以, 分布式一致性的本质在这里就显而易见了 - 那就是让操作在某个副本上发生的顺序符合我们的期望 .

例如, 我们可能读取副本A, 也可能读取副本B, 或者同时读取副本A和B, 如果我们让所有的操作, 包括读和写在两个副本上发生的顺序相同, 那么, 结果必然是一致的, 这是很显然的.

这也是为什么有一些系统, 会把读请求放入 Raft 日志序列中进行同步的原因, 因为不同副本上的 Raft 的日志序列是相同排序的. 一条 Raft 日志序列是全序的 , 但不相关的操作之间可能没有相互依赖和影响, 所以全序没有必要. 例如, 某些日志操作对象 a, 某些日志操作对象 b, 这两类操作之间没有必要排序, 让它们单独排序就可以了, 即所谓的偏序 .

简单的实现方法就是做 Sharding, 把针对不同对象的操作拆分到不同的日志序列中, 也即所谓的 Multi Raft Group.

还有一种实现是不把读操作放到日志序列中, 而是通过停止等待, 等待某一个写操作 在某个副本发生之后, 再执行读操作, 便保证了顺序. 这就是 Read Index 的本质, Read Index 先找出全部副本的最新的共识, 然后等最新的那条共识执行之后, 再执行读操作, 也就必然保证读操作发生在我们期望的那一次写操作 之后.

Related posts:

  1. Paxos和Raft读优化 – Quorum Read 和 Read Index
  2. Raft 为什么不能直接 commit 前任的日志?
  3. Paxos 与分布式强一致性
  4. 什么是 Paxos 的日志空洞?
  5. 分布式存储名词解析 – 一致性
]]>
0
<![CDATA[记一次关于系统性能的有趣讨论]]> http://www.udpwork.com/item/18148.html http://www.udpwork.com/item/18148.html#reviews Sat, 04 Sep 2021 10:29:04 +0800 ideawu http://www.udpwork.com/item/18148.html 有个同事问我:"你开发的分布式数据库系统, 如何避免 scan 扫描操作返回了 pending 事务状态的数据?"

我说:"把数据扫描出来, 然后逐个判断过滤掉 pending 状态的数据."

我感到奇怪, 对于他的问题, 解决方案非常显然啊. 解决方案非常直观, 兵来将挡水来土掩, 我相信他也能想到, 不想要的数据当然要剔除掉, 否则呢? 那么, 他的问题的点在哪?

没错, 他接着问了:"我 scan 的时候只想返回 key, 但是, 要判断状态, 是不是还得去读取 value 解析出状态信息?"

我当时是黑人问号脸:"当然要知道状态信息才能根据状态过滤呀, 不然呢?"

他终于祭出了那个经典的让人哭笑不得的问题:"读取 value, 是不是性能会下降啊?"

且不说无缘无故在没有任何场景支撑的情况下就提出性能问题让人莫名其妙, 这有什么性能问题? 我当然知道, 一次内存拷贝当然比两次内存拷贝性能高, 一次 IO 当然比两次 IO 性能高, 所以呢?

这种凭空想出的所谓"性能问题"有什么可讨论的? 你觉得一次 IO 性能高, 那只能在写入的时候把需要的数据聚簇到一起, 以便将来能一次读取. 也就是在写入的时候多付出成本, 以换取读取的时候节省成本, 道理不是很简单吗?

所以我说:"那就把状态信息放一起, scan 的时候就一起读出来了. 或者把 pending 状态的数据放到别的地方, scan 时自然不返回."

他应该是悟了, 说:"哦...", 然后走了.

我直到现在还是黑人问号脸... 感觉就像经历了一次蜘蛛蚂蚁, 打雷下雨一样的显而易见的因果关系, 感觉他是在寻找银弹.

其实, 系统层面的方案, 并不像冒泡排序和快速排序的算法性能差异那么明显, 系统层面的算法(方案, 策略, 流程)都是跟着需求走的, 功能需求要求怎么做就怎么做, 一就是一, 一之后就是二, 按部就班照着做, 非常直白, 没有一丁点可以偷工减料的地方, 朝三暮四和朝四暮三的障眼法更不可取.

不想要就过滤掉, 需要的就找出来, 不想多次IO就聚簇存储, 硬盘慢就放内存中, 内存不够就加内存或者淘汰数据, 锁粒度太大了那就增加锁数量减小锁粒度, 临界区太长了那就减少临界区长度, 操作次数太多了那就合并操作, 串行化吞吐量不满足需求那就并发多线程, 如果认为多线程调度成本太大那就换多路复用接口...软件系统工程的事, 一切都是那么地自然和直观.

Related posts:

  1. Raft Read Index 的实现
  2. SSDB 采用里程碑式版本发布机制
  3. SSDB 1.6.6 稳定版发布, 支持 hclear/zclear
  4. Paxos 所谓的”幽灵复现”
  5. SSDB增加hlist, zlist命令
]]>
有个同事问我:"你开发的分布式数据库系统, 如何避免 scan 扫描操作返回了 pending 事务状态的数据?"

我说:"把数据扫描出来, 然后逐个判断过滤掉 pending 状态的数据."

我感到奇怪, 对于他的问题, 解决方案非常显然啊. 解决方案非常直观, 兵来将挡水来土掩, 我相信他也能想到, 不想要的数据当然要剔除掉, 否则呢? 那么, 他的问题的点在哪?

没错, 他接着问了:"我 scan 的时候只想返回 key, 但是, 要判断状态, 是不是还得去读取 value 解析出状态信息?"

我当时是黑人问号脸:"当然要知道状态信息才能根据状态过滤呀, 不然呢?"

他终于祭出了那个经典的让人哭笑不得的问题:"读取 value, 是不是性能会下降啊?"

且不说无缘无故在没有任何场景支撑的情况下就提出性能问题让人莫名其妙, 这有什么性能问题? 我当然知道, 一次内存拷贝当然比两次内存拷贝性能高, 一次 IO 当然比两次 IO 性能高, 所以呢?

这种凭空想出的所谓"性能问题"有什么可讨论的? 你觉得一次 IO 性能高, 那只能在写入的时候把需要的数据聚簇到一起, 以便将来能一次读取. 也就是在写入的时候多付出成本, 以换取读取的时候节省成本, 道理不是很简单吗?

所以我说:"那就把状态信息放一起, scan 的时候就一起读出来了. 或者把 pending 状态的数据放到别的地方, scan 时自然不返回."

他应该是悟了, 说:"哦...", 然后走了.

我直到现在还是黑人问号脸... 感觉就像经历了一次蜘蛛蚂蚁, 打雷下雨一样的显而易见的因果关系, 感觉他是在寻找银弹.

其实, 系统层面的方案, 并不像冒泡排序和快速排序的算法性能差异那么明显, 系统层面的算法(方案, 策略, 流程)都是跟着需求走的, 功能需求要求怎么做就怎么做, 一就是一, 一之后就是二, 按部就班照着做, 非常直白, 没有一丁点可以偷工减料的地方, 朝三暮四和朝四暮三的障眼法更不可取.

不想要就过滤掉, 需要的就找出来, 不想多次IO就聚簇存储, 硬盘慢就放内存中, 内存不够就加内存或者淘汰数据, 锁粒度太大了那就增加锁数量减小锁粒度, 临界区太长了那就减少临界区长度, 操作次数太多了那就合并操作, 串行化吞吐量不满足需求那就并发多线程, 如果认为多线程调度成本太大那就换多路复用接口...软件系统工程的事, 一切都是那么地自然和直观.

Related posts:

  1. Raft Read Index 的实现
  2. SSDB 采用里程碑式版本发布机制
  3. SSDB 1.6.6 稳定版发布, 支持 hclear/zclear
  4. Paxos 所谓的”幽灵复现”
  5. SSDB增加hlist, zlist命令
]]>
0
<![CDATA[Some hints on Dataproc]]> http://www.udpwork.com/item/18151.html http://www.udpwork.com/item/18151.html#reviews Fri, 03 Sep 2021 11:54:47 +0800 ROBIN DONG http://www.udpwork.com/item/18151.html
  • When running a job in the cluster of Dataproc, it reported:
  • java.util.concurrent.ExecutionException: java.lang.ClassNotFoundException: Failed to find data source: BIGQUERY.

    The reason is I haven’t added the Jar file for BigQuery. After adding the new Jar file intopropertiesto the template of creating a cluster:

    properties:
              spark:spark.jars: gs://spark-lib/bigquery/spark-bigquery-with-dependencies_2.11-0.18.1.jar

    the job starts to read data from BigQuery tables.

    Remember not to usegs://spark-lib/bigquery/spark-bigquery-latest.jarbecause it will hang your job when you are reading BigQuery tables. Seems even google makes a significant mistake in their cloud platform :p

    2. If a PySpark job needs to use some additional packages in the Dataproc cluster, what should we do?

    Still need to add more items in the template to let it install pip packages:

        clusterName: robin
        config:
          gceClusterConfig:
            metadata:
              enable-cloud-sql-proxy-on-workers: 'false'
              use-cloud-sql-private-ip: 'false'
              PIP_PACKAGES: 'google-cloud-storage google-api-python-client google-auth'
          initializationActions:
          - executableFile: gs://goog-dataproc-initialization-actions-us-central1/python/pip-install.sh
            executionTimeout: 600s

    3. To see how a Hive table be created

    show create table <table>;
    ]]>
  • When running a job in the cluster of Dataproc, it reported:
  • java.util.concurrent.ExecutionException: java.lang.ClassNotFoundException: Failed to find data source: BIGQUERY.

    The reason is I haven’t added the Jar file for BigQuery. After adding the new Jar file intopropertiesto the template of creating a cluster:

    properties:
              spark:spark.jars: gs://spark-lib/bigquery/spark-bigquery-with-dependencies_2.11-0.18.1.jar

    the job starts to read data from BigQuery tables.

    Remember not to usegs://spark-lib/bigquery/spark-bigquery-latest.jarbecause it will hang your job when you are reading BigQuery tables. Seems even google makes a significant mistake in their cloud platform :p

    2. If a PySpark job needs to use some additional packages in the Dataproc cluster, what should we do?

    Still need to add more items in the template to let it install pip packages:

        clusterName: robin
        config:
          gceClusterConfig:
            metadata:
              enable-cloud-sql-proxy-on-workers: 'false'
              use-cloud-sql-private-ip: 'false'
              PIP_PACKAGES: 'google-cloud-storage google-api-python-client google-auth'
          initializationActions:
          - executableFile: gs://goog-dataproc-initialization-actions-us-central1/python/pip-install.sh
            executionTimeout: 600s

    3. To see how a Hive table be created

    show create table <table>;
    ]]>
    0
    <![CDATA[科技爱好者周刊(第 174 期):全能程序员 vs 特长程序员]]> http://www.udpwork.com/item/18150.html http://www.udpwork.com/item/18150.html#reviews Fri, 03 Sep 2021 08:30:50 +0800 阮一峰 http://www.udpwork.com/item/18150.html 这里记录每周值得分享的科技内容,周五发布。

    本杂志开源(GitHub:ruanyf/weekly),欢迎提交 issue,投稿或推荐科技内容。

    周刊讨论区的帖子《谁在招人?》,提供大量程序员就业信息,欢迎访问或发布工作/实习岗位。

    封面图

    浙江普陀山客运中心最近获得2021年度凡尔赛建筑奖。(via

    本周话题:全能程序员 vs 特长程序员

    我读过一些篮球报道,里面说 NBA 球队不喜欢"全能型球员",更喜欢"特长型球员"。

    所谓"特长型球员",指的是其他方面可能都不行,但是某一方面特别突出,比如防守特别好、三分特别准,或者篮板很强等等。球队愿意签这样的球员,关键时刻派上场,没准就能立下奇功。

    反过来,"全能型球员"各项技术都很均衡,防守、进攻、投篮都还可以,能达到平均水平,但是每个方面都不突出,没有明显的过人之处。球队看不上这样的球员,觉得让你上场,赢不了球。

    我马上联想到,软件行业其实也是如此,"全能程序员"的出路,明显不如"特长程序员"。

    如果你什么都会干,前端、后端、数据库、服务器管理都能上手,那么哪里缺人手,就会让你顶上去。但是,一旦遇到疑难问题,又指望不了你,因为你不是那个方面的专家,解决不了。

    反过来,你精通某一项技术,其他都不懂,公司反而会觉得你更有价值。因为你可以把这个技术做得很深,克服技术难点,超过竞争对手,所以你的晋升会快得多。

    我甚至读到过这样一段话:

    "绝大多数成熟的程序员都专攻某一个技术栈,因为这样更容易找到工作。一些专家甚至认为,在不同的技术栈中工作是简历的污点。 "

    所以,大家写简历的时候,千万不要把自己懂的所有技术都写进去,那是简历的扣分项。而只写你掌握最深的那项技术,以及与之相关的工作经历。

    同样的,新人如果想在软件行业长远发展,建议选择一两个技术方向进行专研,成为专家,而不要什么技术都懂一点,但是哪一项都不精通。

    但是,这样也有风险,万一你选择的技术栈走下坡路(比如 PHP),甚至消失了(比如 Flash),你可能被迫要换技术栈,那就惨了。

    最保险的做法,就是选择那些有大公司支持的技术。这就是为什么市场上有那么多 Java 专家和 .Net 专家的原因。

    总之,"全能程序员"在这个行业是不受待见的,被视为"万金油"。如果你不幸属于这一类,那么除了抓紧时间开发特长以外,也不必过分灰心。我觉得,全能程序员也有自己的价值和优势,这个就放在下一次谈吧。

    养老金咨询与规划

    本周有一个理财活动的消息,关于养老金的规划和咨询

    程序员是年轻人群体,对于养老金,大家关心的不多。有些人还有错误的观念,觉得如果工作的年限足够长,公司就会承担你的养老金。

    这种想法不对,养老金跟公司无关,是由社保承担的。我国现行的规定是,只要缴纳养老保险满15年,达到法定退休年龄后(目前是60岁,以后会上调到65岁),就可以从政府领取。所以,大家要关心公司有没有缴纳养老保险,这关系到你以后能不能领到养老金。

    程序员这个职业,流动性非常大,换公司是家常便饭,而且这是一个年龄敏感的职业,很少有人能写代码一直到退休。所以,养老金对于程序员,相对来说有更大的保障意义。

    下面是几个大家普遍比较关心的问题。

    1. 如果提早退休,或者离职后没有再找工作,社会保险怎么处理?
    2. 养老金的金额怎么计算?如何拿到更多的养老金?
    3. 自由职业的程序员,可以领取养老金吗?
    4. 除了国家的养老金,商业的养老保险是否值得考虑?

    这些问题都可以在本次的养老金活动里面咨询。

    这个活动来自一直跟我合作的孙明展老师。他从世界500强保险公司离职后,自己创业,开办了创必承公司,从事理财教育和财务咨询工作。大家搜一下"孙明展"这个公号,上面有多年来很多原创的理财科普文章。

    这一次他们为了推广业务,考虑到周刊读者大多数是程序员,就办了这个《养老金的规划和咨询》活动,一共有200个免费名额。各种财务问题都可以咨询,专业的理财师会为大家提供养老金规划,以及家庭财务保障定制服务,保证不推销任何产品。

    活动的主要内容如下:

    • 养老金目标梳理;
    • 家庭成员保障责任综合评估;
    • 现有保险产品的性价比分析比较;
    • 家庭风险属性评估;
    • 基于家庭状况和需求的养老金规划和家庭保障规划构建。

    微信扫码上面二维码,就可以报名。感兴趣的朋友,欢迎参加。

    科技动态

    1、.com 域名涨价

    从今年9月1日开始,.com 域名的注册费从 $7.85 上调到 $8.39,每年增加54美分。

    全世界的域名由 ICANN (互联网名称与数字地址分配组织)管理,它把 .com 域名的管理权委托给 VeriSign 公司,再由 VeriSign 向域名零售商收取注册费,并将一部分收入转交给 ICANN。

    由于 VeriSign 垄断了 .com 域名的注册权,并且还是一家上市公司,历史上域名注册费一直在上涨。可以预期,未来几年中 .com 域名的注册费还会上涨。

    2、电子咖啡

    很多人工作疲惫时,会喝一杯咖啡振奋精神。一家美国创业公司发明了一种电动剃须刀大小的设备,可以替代咖啡。

    这种设备只要按在脖子上,打开开关,就会向人体释放低压电流,每次持续一毫秒,相当于一次电击。

    它可以刺激迷走神经,促进体内肾上腺素的分泌,起到跟咖啡类似的作用。目前,价格是每个 1250美元。

    3、《堡垒之夜》的虚拟纪念活动

    8月28日是马丁·路德·金发表著名演说《我有一个梦想》的纪念日。今年的这一天,《堡垒之夜》在游戏里面开展了虚拟的纪念活动。

    玩家会在路边看到演讲的视频,还能进入虚拟纪念馆,详细了解。以后,这种虚拟形式的纪念活动,将会越来越多。

    4、

    米其林公司正在测试,货轮上面安装可以升降的大型风帆,充分利用风能,减少柴油燃料的使用,从而抑制温室气体的排放。

    5、预制小屋

    美国拉斯维加斯的一家公司,推出了预制小屋,只要 49,500 美元,你就能立刻拥有一间35平米的房子,里面有卧室、浴室、客厅和厨房,已经全部装修好了。

    这种小屋在房价昂贵的加州引发了轰动,连马斯克都听说了,他就下单购买了一间,要求运到 SpaceX 公司,他想住在里面试试看。

    这件事传出去以后,想要购买这种预制屋的人就更多了,目前已经有5万人登记预购了。

    6、太阳能飞机

    据报道,美国海军正在委托研发一种太阳能载人飞机,可以连续飞行90天不落地。

    这种飞机的机翼长达72米,全部铺满了太阳能板,并且配备了大量电池,存储电能供夜间飞行。2015年,这种飞机的原型曾经环球飞行,创下了117小时52分钟(接近5天)不落地的记录。

    7、算法裁员

    美国的游戏服务公司 Xsolla 的收益今年下降了40%,决定裁掉三分之一的员工,大约150人。这件事的特别之处在于,公司使用软件算法判定,应该裁掉哪些人。

    CEO 的内部邮件这样写道:

    "你收到这封电子邮件,是因为我们的大数据团队分析了你在 Jira、Confluence、Gmail、聊天、文档、仪表板中的活动,并将你标记为不敬业和效率低下的员工。换句话说,当你远程工作时,你并不总是在工作。你们中的许多人可能会感到震惊,不认同软件的算法,但我真的相信 Xsolla 不适合你。"

    文章

    1、500米饱和潜水背后的故事(中文)

    我国最近成功完成500米饱和潜水的陆基实验,9名潜水员进入加压舱,在51个大气压的环境下,停留了176个小时(相当于7天多),为下一步潜水员正式出舱,进入500米的深海做准备。

    2、我怎么加入微软剑桥研究院(中文)

    作者详细记录从亚马逊跳槽到微软剑桥研究院的过程。(@DoctorLai投稿)

    3、如何在 Linux 中实时监控日志文件(中文)

    Linux 系统的日志文件一般位于 /var/log,以 .log 扩展名结尾。本文教你如何在一个窗口里面,同时监控所有日志文件。(@jerrylususu投稿)

    4、使用 React Hooks 分离组件逻辑(英文)

    本文介绍 React Hooks 的正确用法,写得非常好,推荐阅读。它回答了一个基本的问题:Hooks 到底应该用来干什么?

    简单说,Hooks 应该用来将组件的 UI 和逻辑分开。

    5、iOS 的四种设计模式(英文)

    本文分析了 iOS 引入的四种新的手机 UI 设计模式,比如将表单的确认按钮,放置在页面顶部的右上角(上图)。

    6、CSS 中 content 属性的妙用(中文)

    CSS 的 content 属性可以向网页添加文本内容,本文总结了这个属性的用途,并逐一给出示例。

    7、操作系统是什么?(中文)

    本文是《操作系统开发入门基础》系列译文的第一篇,讨论了操作系统的概念,哪些工作属于操作系统的范畴。(@StrokMitream投稿)

    8、Firefox 浏览器引入强化版 Cookie 删除(英文)

    浏览器提供的 Cookie 删除功能,只能删除页面本身的 Cookie,不能删除由该页面引入的其它网站 Cookie。

    Firefox 91 提供了强化版 Cookie 删除,可以将某个网站引入的所有 Cookie 删除。

    9、掌握 Python 网页抓取(英文)

    本文详细介绍如何使用 Python 开发一个爬虫,抓取网页,并且考虑到了很多细节问题,比如多线程并行抓取、防止被屏蔽等等。

    工具

    1、FeedAdd

    一个免费服务,提供微信公众号的 RSS 文件,用户可以自己创建订阅源。(@miscommunication投稿)

    2、PlayCover

    在 M1 架构的 Macbook 上运行 iOS app 时,这个开源工具可以让鼠标和键盘支持 iOS app。

    3、Judo

    一个 Mac 应用,用来设计 iOS app 的页面,不用编写代码,用可视化的方式制作页面原型,可以在手机上预览页面效果。

    4、Mac OS Monterey Web

    使用 Svelte 框架在网页上模拟 Mac OS Monterey 的 UI。

    5、OneDev

    一个开源的 Git 仓库托管软件,类似于 GitHub / GitLab,可以自己架设,特点是资源消耗小。

    6、BackgroundRemover

    一个开源软件,用来移除图像和视频的背景。

    7、js-ziju

    一个自制的编译器,可以将 JS 代码输出为 LLVM IR 格式和 X86 Assembly 语言。(@wizardpisces投稿)

    8、zx

    一个 JS 库,用来在 JS 脚本里面方便地调用 Bash 命令,很适合使用 JS 语法来写 Bash 脚本。

    9、HyperFormula

    Excel 公式的 JS 版,目前有300多个公式。同时,这个库也提供数据操作功能,可以用作电子表格的后端。

    资源

    1、Go 语言入门课:零基础到实战

    极客时间福利课程,零基础讲解 Go 的语法和用法,最后带读者手写一个计算器小程序。《Kubernetes 生产化之路》作者、Go 资深工程师李建强主讲。

    下周一(9月6日)开始,三个晚上直播课,微信添加助理老师报名。

    2、MacOS Bliss

    作者参照 Windows XP 著名的草地壁纸,为 MacOS 和 iOS 也创建了多张类似风格的壁纸,可以免费下载,挺好看的。

    3、如何在 Linux 上玩任天堂 Switch 模拟器(英文)

    这篇文章是详细的操作指南,介绍在 Linux 桌面电脑上面,怎样通过模拟器玩 Switch 游戏。

    4、Minecraft 的岩石和矿物

    Minecraft 游戏出现过各种各样的石头和矿物(上图),这个网站列出游戏里的石头在真实世界的对应物(下图)。

    5、AWS 的简单英语介绍

    AWS(亚马逊网络服务)现在包含50多种产品,官方的介绍有时很难懂,搞不清楚每种产品到底干什么。这个网页使用简单的英语,介绍每一种服务。

    图片

    1、键盘裤子

    使用键盘,总是需要抬起手腕。一个荷兰学生 Eric De Nijs 就想,能否不抬起手腕就能使用键盘。他发现,最方便的方法就是把键盘放在大腿上面,只要坐下来就能使用。

    于是,他发明了一种键盘裤子,键盘做在裤子上面,随时随地可以用。

    2、河狸咬树干

    河狸是中国一级保护动物,有着强壮的门牙,可以咬断树干,再将树干拖回家做巢。下面就是一段河狸咬树干的视频,看着非常纾压。

    文摘

    1、Linux 内核5.14版发布说明

    8月25日是 Linux 操作系统诞生纪念日。

    1991年8月25日,芬兰大学生 Linus 在网上宣布,他写了一个操作系统的内核,欢迎大家提意见。

    30年过去了,Linus 依然在为这个项目忙碌。他按照预定计划,在8月29日发布了内核的5.14版。并且写了下面的发布说明。

    "我意识到,大家一定还忙于30周年的庆祝活动,各种花里胡哨的晚会。但在某个时候,你一定会厌倦那里的浮华、烟花和香槟,穿着礼服也不舒服。这种庆祝活动还将持续数周,但你可能想停下来喘口气。

    如果是这样,我正好为你准备了一个新的内核版本,欢迎来测试和享用。5.14版已经发布,就等着你来试用,别忘了所有那些庆祝活动是为了什么。

    当然,可怜的不知疲倦的内核维护者,是不会有时间参加庆祝活动的。因为对他们来说,这一天只是意味着合并窗口将在明天开始。我们期待还有下一个30年。但是对于其他人,请稍事休息,构建新版本的内核,对其进行测试,然后你就可以回到那个你刚刚离开的、看上去不会结束的聚会上了。"

    2、退出 C++

    斯科特·迈耶斯(Scott Meyers)是著名的 C++ 语言专家,写过《Effective C++》系列著作。

    • 1992年,《Effective C++:50 种改进程序和设计的具体方法》
    • 1995年,《More Effective C++:35 种改进程序和设计的新方法》
    • 1998年,《Effective C++(第二版):改进程序和设计的 50 种特定方法》
    • 2001年,《 有效的 STL:改进标准模板库使用的 50 种具体方法》
    • 2005年,《Effective C++(第三版):55 种改进程序和设计的具体方法》
    • 2010年,《新 C++ (C++11)概述》
    • 2010年,《嵌入式环境中的 Effective C++》
    • 2014年《Effective 现代 C++:42 种改进 C++11 和 C++14 使用的具体方法》

    2009年3月,他获得 Dr. Dobb's 杰出贡献奖。

    2015年,他宣布不再写作 C++ 书籍了。

    2018年,他又宣布不再修正自己著作里面的技术错误了。原因不是他不想修正,而是已经没有能力了。

    "为了修复错误,我必须能够识别它们。但是,我不再相信自己有能力做到这件事。"

    "如大家所知,我在 2015 年底退出了对 C++ 语言的积极参与。在过去的两年半中,我忘记了足够多的语言细节,以至于无法再正确评估关于 C++ 的错误报告。

    C++ 是一种庞大而复杂的语言,有各种复杂和微妙的功能,我不再相信自己会记住所有这些内容。因此,我所能做的就是感谢读者提供的错误报告,但不再更新我的书籍了。我认为这是唯一负责任的行动方案。"

    言论

    1、

    我从互联网上得到的最好的经验之一,就是永远不要复制和粘贴不是自己编写的代码。如果你一定要复制,那就照着它逐字输入,逼着自己思考,这些代码实际上是什么意思。

    --Hacker News 读者

    2、

    推行 996 工作制的心态,可以追溯到制造业时代。一家服装厂提高产量的最简单方法是什么?就是让你的工人加班。

    不幸的是,这不适用于科技公司,程序员在一段代码上花费更多时间,并不意味着写出更好的代码。事实上,处理错误的最佳方法通常是休息一下,然后回来重新阅读代码,你会更容易发现错误。

    --Hacker News 读者,评论中国最高法院判定 996 工作制违法

    3、

    日本和韩国都有法律规定,手机拍照必须有快门声,无法关闭这个声音,目的是防止偷拍。

    --知乎

    4、

    写一本技术书籍,报酬寥寥无几,很可能除了荣誉,你得不到任何东西。

    --《自出版技术书籍》

    5、

    无所畏惧(fearless)不是没有恐惧;而是你依然心怀恐惧,但无论如何都决定要跳进去。

    --Taylor Swift

    历史上的本周

    2020年(第 123 期):互联网公司与湘军的军制

    2019年(第 71 期):名校毕业,不容易创业

    2018年(第 20 期):不读大学的替代方案

    订阅

    这个周刊每周五发布,同步更新在阮一峰的网络日志微信公众号

    微信搜索"阮一峰的网络日志"或者扫描二维码,即可订阅。

    (完)

    文档信息

    • 版权声明:自由转载-非商用-非衍生-保持署名(创意共享3.0许可证
    • 发表日期:2021年9月 3日
    ]]>
    这里记录每周值得分享的科技内容,周五发布。

    本杂志开源(GitHub:ruanyf/weekly),欢迎提交 issue,投稿或推荐科技内容。

    周刊讨论区的帖子《谁在招人?》,提供大量程序员就业信息,欢迎访问或发布工作/实习岗位。

    封面图

    浙江普陀山客运中心最近获得2021年度凡尔赛建筑奖。(via

    本周话题:全能程序员 vs 特长程序员

    我读过一些篮球报道,里面说 NBA 球队不喜欢"全能型球员",更喜欢"特长型球员"。

    所谓"特长型球员",指的是其他方面可能都不行,但是某一方面特别突出,比如防守特别好、三分特别准,或者篮板很强等等。球队愿意签这样的球员,关键时刻派上场,没准就能立下奇功。

    反过来,"全能型球员"各项技术都很均衡,防守、进攻、投篮都还可以,能达到平均水平,但是每个方面都不突出,没有明显的过人之处。球队看不上这样的球员,觉得让你上场,赢不了球。

    我马上联想到,软件行业其实也是如此,"全能程序员"的出路,明显不如"特长程序员"。

    如果你什么都会干,前端、后端、数据库、服务器管理都能上手,那么哪里缺人手,就会让你顶上去。但是,一旦遇到疑难问题,又指望不了你,因为你不是那个方面的专家,解决不了。

    反过来,你精通某一项技术,其他都不懂,公司反而会觉得你更有价值。因为你可以把这个技术做得很深,克服技术难点,超过竞争对手,所以你的晋升会快得多。

    我甚至读到过这样一段话:

    "绝大多数成熟的程序员都专攻某一个技术栈,因为这样更容易找到工作。一些专家甚至认为,在不同的技术栈中工作是简历的污点。 "

    所以,大家写简历的时候,千万不要把自己懂的所有技术都写进去,那是简历的扣分项。而只写你掌握最深的那项技术,以及与之相关的工作经历。

    同样的,新人如果想在软件行业长远发展,建议选择一两个技术方向进行专研,成为专家,而不要什么技术都懂一点,但是哪一项都不精通。

    但是,这样也有风险,万一你选择的技术栈走下坡路(比如 PHP),甚至消失了(比如 Flash),你可能被迫要换技术栈,那就惨了。

    最保险的做法,就是选择那些有大公司支持的技术。这就是为什么市场上有那么多 Java 专家和 .Net 专家的原因。

    总之,"全能程序员"在这个行业是不受待见的,被视为"万金油"。如果你不幸属于这一类,那么除了抓紧时间开发特长以外,也不必过分灰心。我觉得,全能程序员也有自己的价值和优势,这个就放在下一次谈吧。

    养老金咨询与规划

    本周有一个理财活动的消息,关于养老金的规划和咨询

    程序员是年轻人群体,对于养老金,大家关心的不多。有些人还有错误的观念,觉得如果工作的年限足够长,公司就会承担你的养老金。

    这种想法不对,养老金跟公司无关,是由社保承担的。我国现行的规定是,只要缴纳养老保险满15年,达到法定退休年龄后(目前是60岁,以后会上调到65岁),就可以从政府领取。所以,大家要关心公司有没有缴纳养老保险,这关系到你以后能不能领到养老金。

    程序员这个职业,流动性非常大,换公司是家常便饭,而且这是一个年龄敏感的职业,很少有人能写代码一直到退休。所以,养老金对于程序员,相对来说有更大的保障意义。

    下面是几个大家普遍比较关心的问题。

    1. 如果提早退休,或者离职后没有再找工作,社会保险怎么处理?
    2. 养老金的金额怎么计算?如何拿到更多的养老金?
    3. 自由职业的程序员,可以领取养老金吗?
    4. 除了国家的养老金,商业的养老保险是否值得考虑?

    这些问题都可以在本次的养老金活动里面咨询。

    这个活动来自一直跟我合作的孙明展老师。他从世界500强保险公司离职后,自己创业,开办了创必承公司,从事理财教育和财务咨询工作。大家搜一下"孙明展"这个公号,上面有多年来很多原创的理财科普文章。

    这一次他们为了推广业务,考虑到周刊读者大多数是程序员,就办了这个《养老金的规划和咨询》活动,一共有200个免费名额。各种财务问题都可以咨询,专业的理财师会为大家提供养老金规划,以及家庭财务保障定制服务,保证不推销任何产品。

    活动的主要内容如下:

    • 养老金目标梳理;
    • 家庭成员保障责任综合评估;
    • 现有保险产品的性价比分析比较;
    • 家庭风险属性评估;
    • 基于家庭状况和需求的养老金规划和家庭保障规划构建。

    微信扫码上面二维码,就可以报名。感兴趣的朋友,欢迎参加。

    科技动态

    1、.com 域名涨价

    从今年9月1日开始,.com 域名的注册费从 $7.85 上调到 $8.39,每年增加54美分。

    全世界的域名由 ICANN (互联网名称与数字地址分配组织)管理,它把 .com 域名的管理权委托给 VeriSign 公司,再由 VeriSign 向域名零售商收取注册费,并将一部分收入转交给 ICANN。

    由于 VeriSign 垄断了 .com 域名的注册权,并且还是一家上市公司,历史上域名注册费一直在上涨。可以预期,未来几年中 .com 域名的注册费还会上涨。

    2、电子咖啡

    很多人工作疲惫时,会喝一杯咖啡振奋精神。一家美国创业公司发明了一种电动剃须刀大小的设备,可以替代咖啡。

    这种设备只要按在脖子上,打开开关,就会向人体释放低压电流,每次持续一毫秒,相当于一次电击。

    它可以刺激迷走神经,促进体内肾上腺素的分泌,起到跟咖啡类似的作用。目前,价格是每个 1250美元。

    3、《堡垒之夜》的虚拟纪念活动

    8月28日是马丁·路德·金发表著名演说《我有一个梦想》的纪念日。今年的这一天,《堡垒之夜》在游戏里面开展了虚拟的纪念活动。

    玩家会在路边看到演讲的视频,还能进入虚拟纪念馆,详细了解。以后,这种虚拟形式的纪念活动,将会越来越多。

    4、

    米其林公司正在测试,货轮上面安装可以升降的大型风帆,充分利用风能,减少柴油燃料的使用,从而抑制温室气体的排放。

    5、预制小屋

    美国拉斯维加斯的一家公司,推出了预制小屋,只要 49,500 美元,你就能立刻拥有一间35平米的房子,里面有卧室、浴室、客厅和厨房,已经全部装修好了。

    这种小屋在房价昂贵的加州引发了轰动,连马斯克都听说了,他就下单购买了一间,要求运到 SpaceX 公司,他想住在里面试试看。

    这件事传出去以后,想要购买这种预制屋的人就更多了,目前已经有5万人登记预购了。

    6、太阳能飞机

    据报道,美国海军正在委托研发一种太阳能载人飞机,可以连续飞行90天不落地。

    这种飞机的机翼长达72米,全部铺满了太阳能板,并且配备了大量电池,存储电能供夜间飞行。2015年,这种飞机的原型曾经环球飞行,创下了117小时52分钟(接近5天)不落地的记录。

    7、算法裁员

    美国的游戏服务公司 Xsolla 的收益今年下降了40%,决定裁掉三分之一的员工,大约150人。这件事的特别之处在于,公司使用软件算法判定,应该裁掉哪些人。

    CEO 的内部邮件这样写道:

    "你收到这封电子邮件,是因为我们的大数据团队分析了你在 Jira、Confluence、Gmail、聊天、文档、仪表板中的活动,并将你标记为不敬业和效率低下的员工。换句话说,当你远程工作时,你并不总是在工作。你们中的许多人可能会感到震惊,不认同软件的算法,但我真的相信 Xsolla 不适合你。"

    文章

    1、500米饱和潜水背后的故事(中文)

    我国最近成功完成500米饱和潜水的陆基实验,9名潜水员进入加压舱,在51个大气压的环境下,停留了176个小时(相当于7天多),为下一步潜水员正式出舱,进入500米的深海做准备。

    2、我怎么加入微软剑桥研究院(中文)

    作者详细记录从亚马逊跳槽到微软剑桥研究院的过程。(@DoctorLai投稿)

    3、如何在 Linux 中实时监控日志文件(中文)

    Linux 系统的日志文件一般位于 /var/log,以 .log 扩展名结尾。本文教你如何在一个窗口里面,同时监控所有日志文件。(@jerrylususu投稿)

    4、使用 React Hooks 分离组件逻辑(英文)

    本文介绍 React Hooks 的正确用法,写得非常好,推荐阅读。它回答了一个基本的问题:Hooks 到底应该用来干什么?

    简单说,Hooks 应该用来将组件的 UI 和逻辑分开。

    5、iOS 的四种设计模式(英文)

    本文分析了 iOS 引入的四种新的手机 UI 设计模式,比如将表单的确认按钮,放置在页面顶部的右上角(上图)。

    6、CSS 中 content 属性的妙用(中文)

    CSS 的 content 属性可以向网页添加文本内容,本文总结了这个属性的用途,并逐一给出示例。

    7、操作系统是什么?(中文)

    本文是《操作系统开发入门基础》系列译文的第一篇,讨论了操作系统的概念,哪些工作属于操作系统的范畴。(@StrokMitream投稿)

    8、Firefox 浏览器引入强化版 Cookie 删除(英文)

    浏览器提供的 Cookie 删除功能,只能删除页面本身的 Cookie,不能删除由该页面引入的其它网站 Cookie。

    Firefox 91 提供了强化版 Cookie 删除,可以将某个网站引入的所有 Cookie 删除。

    9、掌握 Python 网页抓取(英文)

    本文详细介绍如何使用 Python 开发一个爬虫,抓取网页,并且考虑到了很多细节问题,比如多线程并行抓取、防止被屏蔽等等。

    工具

    1、FeedAdd

    一个免费服务,提供微信公众号的 RSS 文件,用户可以自己创建订阅源。(@miscommunication投稿)

    2、PlayCover

    在 M1 架构的 Macbook 上运行 iOS app 时,这个开源工具可以让鼠标和键盘支持 iOS app。

    3、Judo

    一个 Mac 应用,用来设计 iOS app 的页面,不用编写代码,用可视化的方式制作页面原型,可以在手机上预览页面效果。

    4、Mac OS Monterey Web

    使用 Svelte 框架在网页上模拟 Mac OS Monterey 的 UI。

    5、OneDev

    一个开源的 Git 仓库托管软件,类似于 GitHub / GitLab,可以自己架设,特点是资源消耗小。

    6、BackgroundRemover

    一个开源软件,用来移除图像和视频的背景。

    7、js-ziju

    一个自制的编译器,可以将 JS 代码输出为 LLVM IR 格式和 X86 Assembly 语言。(@wizardpisces投稿)

    8、zx

    一个 JS 库,用来在 JS 脚本里面方便地调用 Bash 命令,很适合使用 JS 语法来写 Bash 脚本。

    9、HyperFormula

    Excel 公式的 JS 版,目前有300多个公式。同时,这个库也提供数据操作功能,可以用作电子表格的后端。

    资源

    1、Go 语言入门课:零基础到实战

    极客时间福利课程,零基础讲解 Go 的语法和用法,最后带读者手写一个计算器小程序。《Kubernetes 生产化之路》作者、Go 资深工程师李建强主讲。

    下周一(9月6日)开始,三个晚上直播课,微信添加助理老师报名。

    2、MacOS Bliss

    作者参照 Windows XP 著名的草地壁纸,为 MacOS 和 iOS 也创建了多张类似风格的壁纸,可以免费下载,挺好看的。

    3、如何在 Linux 上玩任天堂 Switch 模拟器(英文)

    这篇文章是详细的操作指南,介绍在 Linux 桌面电脑上面,怎样通过模拟器玩 Switch 游戏。

    4、Minecraft 的岩石和矿物

    Minecraft 游戏出现过各种各样的石头和矿物(上图),这个网站列出游戏里的石头在真实世界的对应物(下图)。

    5、AWS 的简单英语介绍

    AWS(亚马逊网络服务)现在包含50多种产品,官方的介绍有时很难懂,搞不清楚每种产品到底干什么。这个网页使用简单的英语,介绍每一种服务。

    图片

    1、键盘裤子

    使用键盘,总是需要抬起手腕。一个荷兰学生 Eric De Nijs 就想,能否不抬起手腕就能使用键盘。他发现,最方便的方法就是把键盘放在大腿上面,只要坐下来就能使用。

    于是,他发明了一种键盘裤子,键盘做在裤子上面,随时随地可以用。

    2、河狸咬树干

    河狸是中国一级保护动物,有着强壮的门牙,可以咬断树干,再将树干拖回家做巢。下面就是一段河狸咬树干的视频,看着非常纾压。

    文摘

    1、Linux 内核5.14版发布说明

    8月25日是 Linux 操作系统诞生纪念日。

    1991年8月25日,芬兰大学生 Linus 在网上宣布,他写了一个操作系统的内核,欢迎大家提意见。

    30年过去了,Linus 依然在为这个项目忙碌。他按照预定计划,在8月29日发布了内核的5.14版。并且写了下面的发布说明。

    "我意识到,大家一定还忙于30周年的庆祝活动,各种花里胡哨的晚会。但在某个时候,你一定会厌倦那里的浮华、烟花和香槟,穿着礼服也不舒服。这种庆祝活动还将持续数周,但你可能想停下来喘口气。

    如果是这样,我正好为你准备了一个新的内核版本,欢迎来测试和享用。5.14版已经发布,就等着你来试用,别忘了所有那些庆祝活动是为了什么。

    当然,可怜的不知疲倦的内核维护者,是不会有时间参加庆祝活动的。因为对他们来说,这一天只是意味着合并窗口将在明天开始。我们期待还有下一个30年。但是对于其他人,请稍事休息,构建新版本的内核,对其进行测试,然后你就可以回到那个你刚刚离开的、看上去不会结束的聚会上了。"

    2、退出 C++

    斯科特·迈耶斯(Scott Meyers)是著名的 C++ 语言专家,写过《Effective C++》系列著作。

    • 1992年,《Effective C++:50 种改进程序和设计的具体方法》
    • 1995年,《More Effective C++:35 种改进程序和设计的新方法》
    • 1998年,《Effective C++(第二版):改进程序和设计的 50 种特定方法》
    • 2001年,《 有效的 STL:改进标准模板库使用的 50 种具体方法》
    • 2005年,《Effective C++(第三版):55 种改进程序和设计的具体方法》
    • 2010年,《新 C++ (C++11)概述》
    • 2010年,《嵌入式环境中的 Effective C++》
    • 2014年《Effective 现代 C++:42 种改进 C++11 和 C++14 使用的具体方法》

    2009年3月,他获得 Dr. Dobb's 杰出贡献奖。

    2015年,他宣布不再写作 C++ 书籍了。

    2018年,他又宣布不再修正自己著作里面的技术错误了。原因不是他不想修正,而是已经没有能力了。

    "为了修复错误,我必须能够识别它们。但是,我不再相信自己有能力做到这件事。"

    "如大家所知,我在 2015 年底退出了对 C++ 语言的积极参与。在过去的两年半中,我忘记了足够多的语言细节,以至于无法再正确评估关于 C++ 的错误报告。

    C++ 是一种庞大而复杂的语言,有各种复杂和微妙的功能,我不再相信自己会记住所有这些内容。因此,我所能做的就是感谢读者提供的错误报告,但不再更新我的书籍了。我认为这是唯一负责任的行动方案。"

    言论

    1、

    我从互联网上得到的最好的经验之一,就是永远不要复制和粘贴不是自己编写的代码。如果你一定要复制,那就照着它逐字输入,逼着自己思考,这些代码实际上是什么意思。

    --Hacker News 读者

    2、

    推行 996 工作制的心态,可以追溯到制造业时代。一家服装厂提高产量的最简单方法是什么?就是让你的工人加班。

    不幸的是,这不适用于科技公司,程序员在一段代码上花费更多时间,并不意味着写出更好的代码。事实上,处理错误的最佳方法通常是休息一下,然后回来重新阅读代码,你会更容易发现错误。

    --Hacker News 读者,评论中国最高法院判定 996 工作制违法

    3、

    日本和韩国都有法律规定,手机拍照必须有快门声,无法关闭这个声音,目的是防止偷拍。

    --知乎

    4、

    写一本技术书籍,报酬寥寥无几,很可能除了荣誉,你得不到任何东西。

    --《自出版技术书籍》

    5、

    无所畏惧(fearless)不是没有恐惧;而是你依然心怀恐惧,但无论如何都决定要跳进去。

    --Taylor Swift

    历史上的本周

    2020年(第 123 期):互联网公司与湘军的军制

    2019年(第 71 期):名校毕业,不容易创业

    2018年(第 20 期):不读大学的替代方案

    订阅

    这个周刊每周五发布,同步更新在阮一峰的网络日志微信公众号

    微信搜索"阮一峰的网络日志"或者扫描二维码,即可订阅。

    (完)

    文档信息

    • 版权声明:自由转载-非商用-非衍生-保持署名(创意共享3.0许可证
    • 发表日期:2021年9月 3日
    ]]>
    0
    <![CDATA[Binlog 和 Redolog 的区别]]> http://www.udpwork.com/item/18149.html http://www.udpwork.com/item/18149.html#reviews Thu, 02 Sep 2021 21:29:51 +0800 ideawu http://www.udpwork.com/item/18149.html 在开发分布式数据库的过程中, Binlog 和 Redolog 是非常重要的两个概念, 两者的作用似乎相同, 但实际上各有各的使用场景. 从多副本复制一致性的角度看, Binlog 用于强一致性, Redolog 用于最终一致性.

    Binlog 可包含非幂等的指令, 例如 incr 指令. Redolog 只能包含幂等的指令, 例如 set 指令.

    全球跨地域同步最终一致, 能不能复制 Binlog 呢? 绝对不行! 使用 incr 和 set 指令的组合, 在不同的地域写入数据, 很容易就能发现可造成数据不一致(相同)的场景, 而且几乎无法避免(除非副本带有回滚功能). 而如果同步的是 Redolog 的话, 通过复合时间戳, 是可以实现多副本的最终一致的.

    对于强一致的多副本, 能不能复制 Redolog 呢? 似乎是可行的. 例如, 收到 incr 指令, 可以先转换成对应的 set 指令. 但是, 共识过程可能耗费较长时间, 如果这时再来一个 incr 指令, 则必须将这个指令阻塞(因为两个指令有依赖), 否则生成的 set 指令将是错误的. 而如果复制的是 Binlog, 则没有这个问题, 两个 incr 指令可以并发地进行共识流程.

    如果你还是不死心, 还是想在强一致性的多副本之间复制 Redolog, 行不行? 刚才得到的问题也有解决方案, 不过, 复杂度太大, 可能的解决方案也许还要引入更多的问题, 你最好放弃.

    串行化是简化问题的最高效方案, 可以简化依赖的处理, 避免不必要的引用. 所以, 越早串行化越好, 对 Binlog 达成共识, 可以避免对 Redolog 达成共识可能造成的回滚问题. 一条 Binlog 如果共识不了, 换个位置尝试共识即可, 计算成本很小. 如果生成 Redolog 再共识, 一旦共识不了, 就要回滚重新生成 Redolog 再尝试共识.

    Related posts:

    1. Binlog, Redolog 在分布式数据库系统中的应用
    2. MySQL binlog查看和清理
    3. 数据库内核的并发控制
    4. 使用 SSDB 来实现操作频率限制
    5. SSDB源码分析 – 主从和多主同步原理解析
    ]]>
    在开发分布式数据库的过程中, Binlog 和 Redolog 是非常重要的两个概念, 两者的作用似乎相同, 但实际上各有各的使用场景. 从多副本复制一致性的角度看, Binlog 用于强一致性, Redolog 用于最终一致性.

    Binlog 可包含非幂等的指令, 例如 incr 指令. Redolog 只能包含幂等的指令, 例如 set 指令.

    全球跨地域同步最终一致, 能不能复制 Binlog 呢? 绝对不行! 使用 incr 和 set 指令的组合, 在不同的地域写入数据, 很容易就能发现可造成数据不一致(相同)的场景, 而且几乎无法避免(除非副本带有回滚功能). 而如果同步的是 Redolog 的话, 通过复合时间戳, 是可以实现多副本的最终一致的.

    对于强一致的多副本, 能不能复制 Redolog 呢? 似乎是可行的. 例如, 收到 incr 指令, 可以先转换成对应的 set 指令. 但是, 共识过程可能耗费较长时间, 如果这时再来一个 incr 指令, 则必须将这个指令阻塞(因为两个指令有依赖), 否则生成的 set 指令将是错误的. 而如果复制的是 Binlog, 则没有这个问题, 两个 incr 指令可以并发地进行共识流程.

    如果你还是不死心, 还是想在强一致性的多副本之间复制 Redolog, 行不行? 刚才得到的问题也有解决方案, 不过, 复杂度太大, 可能的解决方案也许还要引入更多的问题, 你最好放弃.

    串行化是简化问题的最高效方案, 可以简化依赖的处理, 避免不必要的引用. 所以, 越早串行化越好, 对 Binlog 达成共识, 可以避免对 Redolog 达成共识可能造成的回滚问题. 一条 Binlog 如果共识不了, 换个位置尝试共识即可, 计算成本很小. 如果生成 Redolog 再共识, 一旦共识不了, 就要回滚重新生成 Redolog 再尝试共识.

    Related posts:

    1. Binlog, Redolog 在分布式数据库系统中的应用
    2. MySQL binlog查看和清理
    3. 数据库内核的并发控制
    4. 使用 SSDB 来实现操作频率限制
    5. SSDB源码分析 – 主从和多主同步原理解析
    ]]>
    0
    <![CDATA[Go泛型是怎么实现的?]]> http://www.udpwork.com/item/18153.html http://www.udpwork.com/item/18153.html#reviews Tue, 31 Aug 2021 18:36:01 +0800 鸟窝 http://www.udpwork.com/item/18153.html Go 1.17中你就可以使用泛型了,可以参考我3月份的文章:Go 泛型尝鲜, 编译的时候需要加-gcflags=-G=3参数,而当前master分支,默认已经支持泛型,不需要加-G=3参数了。

    你可以通过下面的步骤尝试go最新分支:

    12
    go get golang.org/dl/gotipgotip download

    编译代码的时候使用gotip替换go命令即可。

    随着Go 1.17的发布,最近也涌现了很多的介绍Go泛型的文章,基本上都是简单介绍的文章。

    最近Go泛型的变化是增加了两个操作符:~和|:

    • an approximation element~Trestricts to all types whose underlying type is T: 代表底层类型是T
    • a union elementT1 | T2 | ...restricts to any of the listed elements: 代表或,类型列表之一。

    这些不是我想介绍的内容,今天我肝一篇介绍Go泛型实现原理的文章,介绍Go泛型实现的方案。

    对于一个函数func Echo[T any](t T){},Go编译器到底编译成了什么代码?

    简单的说,当前Go泛型实现的方案和下图中的方案一样:

    在国内的老破小小区的楼道中常见的一种高科技印刷技术,通过一个镂花模板,为每一种类型生成特化的类型,这个术语叫做stenciling。

    但是如果再说多一点,那么就应该从 Taylor和Griesemer说起。

    Go泛型提案中关于泛型实现的介绍

    Go的泛型有别于其它语言的方案,在Go语言中泛型叫做Type Parameter(类型参数).

    Taylor和Griesemer的提案Type Parameters Proposal更多的是泛型呈现形式和影响的思考,对具体的实现涉及甚少。

    无论什么编程语言,根据Russ Cox的观察,实现泛型至少要面对下面三条困境之一,那还是在2009年:

    • 拖累程序员:比如C语言,增加了程序员的负担,需要曲折的实现,但是不对增加语言的复杂性
    • 拖累编译器: 比如C++编程语言,增加了编译器的负担,可能会产生很多冗余的代码,重复的代码还需要编译器斟酌删除,编译的文件可能非常大。Rust的泛型也属于这一类。
    • 拖累执行时间:比如Java,将一些装箱成Object,进行类型擦除。虽然代码没啥冗余了,空间节省了,但是需要装箱拆箱操作,代码效率低。

    很显然, Go语言至简的设计哲学让它的泛型实现不会选择增加程序员的负担的道路,所以它会在第二和第三种方案中做选择。虽然提案中没有最终说明它选择了哪种方案,但是从实际编译的代码可以看出,它选择的是第二种方案。

    三个方案

    Keith H. Randall, MIT的博士,现在在Google/Go team做泛型方面的开发,提出了Go泛型实现的三个方案:

    字典

    在编译时生成一组实例化的字典,在实例话一个泛型函数的时候会使用字典进行蜡印(stencile)。

    当为泛型函数生成代码的时候,会生成唯一的一块代码,并且会在参数列表中增加一个字典做参数,就像方法会把receiver当成一个参数传入。字典包含为类型参数实例化的类型信息。

    字典在编译时生成,存放在只读的data section中。

    当然字段可以当成第一个参数,或者最后一个参数,或者放入一个独占的寄存器。

    当然这种方案还有依赖问题,比如字典递归的问题,更重要的是,它对性能可能有比较大的影响,比如一个实例化类型int,x=y可能通过寄存器复制就可以了,但是泛型必须通过memmove。

    蜡印(Stenciling)

    或者翻译成用模板印等。

    就像下面的动图一样,同一个泛型函数,为每一个实例化的类型参数生成一套独立的代码,感觉和rust的泛型特化一样。

    这种方案和上面的字典方案正好相反。

    比如下面一个泛型方法:

    123
    func f[T1, T2 any](x int, y T1) T2 {    ...}

    如果有两个不同的类型实例化的调用:

    12
    var a float64 = f[int, float64](7, 8.0)var b struct{f int} = f[complex128, struct{f int}](3, 1+1i)

    那么这个方案会生成两套代码:

    123456
    func f1(x int, y int) float64 {    ... identical bodies ...}func f2(x int, y complex128) struct{f int} {    ... identical bodies ...}

    因为编译f时是不知道它的实例化类型的,只有在调用它时才知道它的实例化的类型,所以需要在调用时编译f。对于相同实例化类型的多个调用,同一个package下编译器可以识别出来是一样的,只生成一个代码就可以了,但是不同的package就不简单了,这些函数表标记为DUPOK,所以链接器会丢掉重复的函数实现。

    这种策略需要更多的编译时间,因为需要编译泛型函数多次。因为对于同一个泛型函数,每种类型需要单独的一份编译的代码,如果类型非常多,编译的文件可能非常大,而且性能也比较差。

    混合方案(GC Shape Stenciling)

    混合前面的两种方案。

    对于实例类型的shape相同的情况,只生成一份代码,对于shape类型相同的类型,使用字典区分类型的不同行为。

    这种方案介于前两者之间。

    啥叫shape?

    类型的shape是它对内存分配器/垃圾回收器呈现的方式,包括它的大小、所需的对齐方式、以及类型哪些部分包含指针。

    每一个唯一的shape会产生一份代码,每份代码携带一个字典,包含了实例化类型的信息。

    这种方案的问题是到底能带来多大的收益,它会变得有多慢,以及其它的一些问题。

    从当前的反编译的代码看,当前Go采用的是第二种方案,尽管名称中已经带了shape、dict的标志,或许,Go的泛型方案还在进化之中,进化到第三种方案或者其它方案也不是没有可能。

    接下来我们看一个例子,看看Go泛型的方案是怎么实现的。

    例子

    下面是一个简单的例子,有一个泛型函数func echo[T any](t T) string {return fmt.Sprintf("%v", t)},使用不同的几种实例化类型去调用它,并且使用shape一样的int32和uint32做为实例化类型。

    1234567891011121314151617181920
    package genericimport (	"fmt"	"time")func echo[T any](t T) string {	return fmt.Sprintf("%v", t)}func Test() {	echo(0)	echo(int32(0))	echo(uint32(0))	echo(uint64(0))	echo("hello")	echo(struct{}{})	echo(time.Now())}

    反编译后代码非常长,精简如下。编译的时候禁止优化和内联,否则实例化的代码内联后看不到效果了。

    可以看到函数echo编译成了不同的函数:"".echo[.shape.int]、"".echo[.shape.int32]、"".echo[.shape.uint32]、"".echo[.shape.uint64]、"".echo[.shape.string]、"".echo[.shape.struct{}]、"".echo[.shape.struct{ time.wall uint64; time.ext int64; time.loc *time.Location }]不同的函数,即使shape一样的类型(int32、uint32)。 调用这些函数时,是通过""..dict.echo[uint64]这种方式调用的。

    所以我谨慎怀疑,Go的泛型方式在逐步的向第三种方案进化。

    123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121
    # command-line-arguments"".Test STEXT size=185 args=0x0 locals=0x48 funcid=0x0	0x0000 00000 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:12)	TEXT	"".Test(SB), ABIInternal, $72-0	"".Test STEXT size=185 args=0x0 locals=0x48 funcid=0x0	0x0000 00000 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:12)	TEXT	"".Test(SB), ABIInternal, $72-0	0x0000 00000 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:12)	CMPQ	SP, 16(R14)	0x0004 00004 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:12)	PCDATA	$0, $-2	0x0004 00004 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:12)	JLS	175	0x000a 00010 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:12)	PCDATA	$0, $-1	0x000a 00010 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:12)	SUBQ	$72, SP	0x000e 00014 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:12)	MOVQ	BP, 64(SP)	0x0013 00019 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:12)	LEAQ	64(SP), BP	0x0018 00024 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:12)	FUNCDATA	$0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)	0x0018 00024 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:12)	FUNCDATA	$1, gclocals·54241e171da8af6ae173d69da0236748(SB)	0x0018 00024 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:13)	LEAQ	""..dict.echo[int](SB), AX	0x001f 00031 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:13)	XORL	BX, BX	0x0021 00033 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:13)	PCDATA	$1, $0	0x0021 00033 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:13)	CALL	"".echo[.shape.int](SB)	0x0026 00038 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:14)	LEAQ	""..dict.echo[int32](SB), AX	0x002d 00045 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:14)	XORL	BX, BX	0x002f 00047 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:14)	CALL	"".echo[.shape.int32](SB)	0x0034 00052 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:15)	LEAQ	""..dict.echo[uint32](SB), AX	0x003b 00059 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:15)	XORL	BX, BX	0x003d 00061 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:15)	NOP	0x0040 00064 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:15)	CALL	"".echo[.shape.uint32](SB)	0x0045 00069 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:16)	LEAQ	""..dict.echo[uint64](SB), AX	0x004c 00076 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:16)	XORL	BX, BX	0x004e 00078 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:16)	CALL	"".echo[.shape.uint64](SB)	0x0053 00083 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:17)	LEAQ	""..dict.echo[string](SB), AX	0x005a 00090 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:17)	LEAQ	go.string."hello"(SB), BX	0x0061 00097 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:17)	MOVL	$5, CX	0x0066 00102 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:17)	CALL	"".echo[.shape.string](SB)	0x006b 00107 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:18)	LEAQ	""..dict.echo[struct{}](SB), AX	0x0072 00114 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:18)	CALL	"".echo[.shape.struct{}](SB)	0x0077 00119 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:19)	CALL	time.Now(SB)	0x007c 00124 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:19)	MOVQ	AX, ""..autotmp_0+40(SP)	0x0081 00129 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:19)	MOVQ	BX, ""..autotmp_0+48(SP)	0x0086 00134 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:19)	MOVQ	CX, ""..autotmp_0+56(SP)	0x008b 00139 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:19)	MOVQ	CX, DI	0x008e 00142 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:19)	MOVQ	BX, CX	0x0091 00145 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:19)	MOVQ	AX, BX	0x0094 00148 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:19)	LEAQ	""..dict.echo[time.Time](SB), AX	0x009b 00155 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:19)	NOP	0x00a0 00160 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:19)	CALL	"".echo[.shape.struct{ time.wall uint64; time.ext int64; time.loc *time.Location }](SB)	0x00a5 00165 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:20)	MOVQ	64(SP), BP	0x00aa 00170 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:20)	ADDQ	$72, SP	0x00ae 00174 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:20)	RET	0x00af 00175 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:20)	NOP	0x00af 00175 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:12)	PCDATA	$1, $-1	0x00af 00175 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:12)	PCDATA	$0, $-2	0x00af 00175 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:12)	CALL	runtime.morestack_noctxt(SB)	0x00b4 00180 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:12)	PCDATA	$0, $-1	0x00b4 00180 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:12)	JMP	0    ................."".echo[.shape.int] STEXT dupok size=268 args=0x10 locals=0x88 funcid=0x0	0x0000 00000 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:8)	TEXT	"".echo[.shape.int](SB), DUPOK|ABIInternal, $136-16	.................	0x00c2 00194 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:9)	MOVQ	DI, SI	0x00c5 00197 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:9)	PCDATA	$1, $0	0x00c5 00197 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:9)	CALL	fmt.Sprintf(SB)    ................."".echo[.shape.int32] STEXT dupok size=266 args=0x10 locals=0x88 funcid=0x0	0x0000 00000 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:8)	TEXT	"".echo[.shape.int32](SB), DUPOK|ABIInternal, $136-16	.................	0x00bd 00189 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:9)	MOVL	$1, DI	0x00c2 00194 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:9)	MOVQ	DI, SI	0x00c5 00197 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:9)	PCDATA	$1, $0	0x00c5 00197 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:9)	CALL	fmt.Sprintf(SB)    ................."".echo[.shape.uint32] STEXT dupok size=266 args=0x10 locals=0x88 funcid=0x0	0x0000 00000 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:8)	TEXT	"".echo[.shape.uint32](SB), DUPOK|ABIInternal, $136-16	.................	0x00c5 00197 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:9)	PCDATA	$1, $0	0x00c5 00197 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:9)	CALL	fmt.Sprintf(SB)    ................."".echo[.shape.uint64] STEXT dupok size=268 args=0x10 locals=0x88 funcid=0x0	0x0000 00000 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:8)	TEXT	"".echo[.shape.uint64](SB), DUPOK|ABIInternal, $136-16	.................	0x00c2 00194 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:9)	MOVQ	DI, SI	0x00c5 00197 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:9)	PCDATA	$1, $0	0x00c5 00197 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:9)	CALL	fmt.Sprintf(SB)    ................."".echo[.shape.string] STEXT dupok size=295 args=0x18 locals=0x88 funcid=0x0	0x0000 00000 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:8)	TEXT	"".echo[.shape.string](SB), DUPOK|ABIInternal, $136-24	.................	0x00d6 00214 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:9)	PCDATA	$1, $2	0x00d6 00214 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:9)	CALL	fmt.Sprintf(SB)    ................."".echo[.shape.struct{}] STEXT dupok size=208 args=0x8 locals=0x88 funcid=0x0	0x0000 00000 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:8)	TEXT	"".echo[.shape.struct{}](SB), DUPOK|ABIInternal, $136-8	.................	0x0093 00147 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:9)	PCDATA	$1, $0	0x0093 00147 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:9)	CALL	fmt.Sprintf(SB)    .................	0x00cb 00203 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:8)	JMP	0	................."".echo[.shape.struct{ time.wall uint64; time.ext int64; time.loc *time.Location }] STEXT dupok size=364 args=0x20 locals=0xa0 funcid=0x0	0x0000 00000 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:8)	TEXT	"".echo[.shape.struct{ time.wall uint64; time.ext int64; time.loc *time.Location }](SB), DUPOK|ABIInternal, $160-32	.................	0x00c5 00197 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:9)	CMPL	runtime.writeBarrier(SB), $0	0x00cc 00204 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:9)	JEQ	208	0x00ce 00206 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:9)	JMP	214	0x00d0 00208 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:9)	MOVQ	AX, 8(CX)	0x00d4 00212 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:9)	JMP	221	0x00d6 00214 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:9)	CALL	runtime.gcWriteBarrier(SB)	.................	0x0167 00359 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:8)	JMP	0	..............."".echo[.shape.string].stkobj SRODATA static size=32	......."".echo[.shape.string].arginfo1 SRODATA static dupok size=9	.......           .........."".echo[.shape.struct{}].stkobj SRODATA static size=32	......."".echo[.shape.struct{}].arginfo1 SRODATA static dupok size=5	......."".echo[.shape.struct{ time.wall uint64; time.ext int64; time.loc *time.Location }].stkobj SRODATA static size=56	......"".echo[.shape.struct{ time.wall uint64; time.ext int64; time.loc *time.Location }].arginfo1 SRODATA static dupok size=11	0x0000 00 08 fe 08 08 10 08 18 08 fd ff                 ...........

    泛型的性能

    写一个简单的benchmark程序,没看到明显的性能变化。

    12345678910111213141516171819202122232425262728293031323334353637
    package bench_testimport (	"fmt"    "testing")func BenchmarkAdd_Generic(b *testing.B) {	for i := 0; i < b.N; i++ {		add(i, i)	}}func BenchmarkAdd_NonGeneric(b *testing.B) {	for i := 0; i < b.N; i++ {		addInt(i, i)	}}type Addable interface {	int}func add[T Addable](a, b T) T {	return a + b}func addInt(a, b int) int {	return a + b}func main() {	fmt.Println(add(1, 2))	fmt.Println(addInt(1, 2))}

    参考文档

    1. https://github.com/golang/proposal/blob/master/design/generics-implementation-dictionaries.md
    2. https://github.com/golang/proposal/blob/master/design/generics-implementation-gcshape.md
    3. https://github.com/golang/proposal/blob/master/design/generics-implementation-stenciling.md
    4. https://github.com/golang/proposal/blob/master/design/43651-type-parameters.md
    5. https://docs.google.com/document/d/1vrAy9gMpMoS3uaVphB32uVXX4pi-HnNjkMEgyAHX4N4/view#
    ]]>
    Go 1.17中你就可以使用泛型了,可以参考我3月份的文章:Go 泛型尝鲜, 编译的时候需要加-gcflags=-G=3参数,而当前master分支,默认已经支持泛型,不需要加-G=3参数了。

    你可以通过下面的步骤尝试go最新分支:

    12
    go get golang.org/dl/gotipgotip download

    编译代码的时候使用gotip替换go命令即可。

    随着Go 1.17的发布,最近也涌现了很多的介绍Go泛型的文章,基本上都是简单介绍的文章。

    最近Go泛型的变化是增加了两个操作符:~和|:

    • an approximation element~Trestricts to all types whose underlying type is T: 代表底层类型是T
    • a union elementT1 | T2 | ...restricts to any of the listed elements: 代表或,类型列表之一。

    这些不是我想介绍的内容,今天我肝一篇介绍Go泛型实现原理的文章,介绍Go泛型实现的方案。

    对于一个函数func Echo[T any](t T){},Go编译器到底编译成了什么代码?

    简单的说,当前Go泛型实现的方案和下图中的方案一样:

    在国内的老破小小区的楼道中常见的一种高科技印刷技术,通过一个镂花模板,为每一种类型生成特化的类型,这个术语叫做stenciling。

    但是如果再说多一点,那么就应该从 Taylor和Griesemer说起。

    Go泛型提案中关于泛型实现的介绍

    Go的泛型有别于其它语言的方案,在Go语言中泛型叫做Type Parameter(类型参数).

    Taylor和Griesemer的提案Type Parameters Proposal更多的是泛型呈现形式和影响的思考,对具体的实现涉及甚少。

    无论什么编程语言,根据Russ Cox的观察,实现泛型至少要面对下面三条困境之一,那还是在2009年:

    • 拖累程序员:比如C语言,增加了程序员的负担,需要曲折的实现,但是不对增加语言的复杂性
    • 拖累编译器: 比如C++编程语言,增加了编译器的负担,可能会产生很多冗余的代码,重复的代码还需要编译器斟酌删除,编译的文件可能非常大。Rust的泛型也属于这一类。
    • 拖累执行时间:比如Java,将一些装箱成Object,进行类型擦除。虽然代码没啥冗余了,空间节省了,但是需要装箱拆箱操作,代码效率低。

    很显然, Go语言至简的设计哲学让它的泛型实现不会选择增加程序员的负担的道路,所以它会在第二和第三种方案中做选择。虽然提案中没有最终说明它选择了哪种方案,但是从实际编译的代码可以看出,它选择的是第二种方案。

    三个方案

    Keith H. Randall, MIT的博士,现在在Google/Go team做泛型方面的开发,提出了Go泛型实现的三个方案:

    字典

    在编译时生成一组实例化的字典,在实例话一个泛型函数的时候会使用字典进行蜡印(stencile)。

    当为泛型函数生成代码的时候,会生成唯一的一块代码,并且会在参数列表中增加一个字典做参数,就像方法会把receiver当成一个参数传入。字典包含为类型参数实例化的类型信息。

    字典在编译时生成,存放在只读的data section中。

    当然字段可以当成第一个参数,或者最后一个参数,或者放入一个独占的寄存器。

    当然这种方案还有依赖问题,比如字典递归的问题,更重要的是,它对性能可能有比较大的影响,比如一个实例化类型int,x=y可能通过寄存器复制就可以了,但是泛型必须通过memmove。

    蜡印(Stenciling)

    或者翻译成用模板印等。

    就像下面的动图一样,同一个泛型函数,为每一个实例化的类型参数生成一套独立的代码,感觉和rust的泛型特化一样。

    这种方案和上面的字典方案正好相反。

    比如下面一个泛型方法:

    123
    func f[T1, T2 any](x int, y T1) T2 {    ...}

    如果有两个不同的类型实例化的调用:

    12
    var a float64 = f[int, float64](7, 8.0)var b struct{f int} = f[complex128, struct{f int}](3, 1+1i)

    那么这个方案会生成两套代码:

    123456
    func f1(x int, y int) float64 {    ... identical bodies ...}func f2(x int, y complex128) struct{f int} {    ... identical bodies ...}

    因为编译f时是不知道它的实例化类型的,只有在调用它时才知道它的实例化的类型,所以需要在调用时编译f。对于相同实例化类型的多个调用,同一个package下编译器可以识别出来是一样的,只生成一个代码就可以了,但是不同的package就不简单了,这些函数表标记为DUPOK,所以链接器会丢掉重复的函数实现。

    这种策略需要更多的编译时间,因为需要编译泛型函数多次。因为对于同一个泛型函数,每种类型需要单独的一份编译的代码,如果类型非常多,编译的文件可能非常大,而且性能也比较差。

    混合方案(GC Shape Stenciling)

    混合前面的两种方案。

    对于实例类型的shape相同的情况,只生成一份代码,对于shape类型相同的类型,使用字典区分类型的不同行为。

    这种方案介于前两者之间。

    啥叫shape?

    类型的shape是它对内存分配器/垃圾回收器呈现的方式,包括它的大小、所需的对齐方式、以及类型哪些部分包含指针。

    每一个唯一的shape会产生一份代码,每份代码携带一个字典,包含了实例化类型的信息。

    这种方案的问题是到底能带来多大的收益,它会变得有多慢,以及其它的一些问题。

    从当前的反编译的代码看,当前Go采用的是第二种方案,尽管名称中已经带了shape、dict的标志,或许,Go的泛型方案还在进化之中,进化到第三种方案或者其它方案也不是没有可能。

    接下来我们看一个例子,看看Go泛型的方案是怎么实现的。

    例子

    下面是一个简单的例子,有一个泛型函数func echo[T any](t T) string {return fmt.Sprintf("%v", t)},使用不同的几种实例化类型去调用它,并且使用shape一样的int32和uint32做为实例化类型。

    1234567891011121314151617181920
    package genericimport (	"fmt"	"time")func echo[T any](t T) string {	return fmt.Sprintf("%v", t)}func Test() {	echo(0)	echo(int32(0))	echo(uint32(0))	echo(uint64(0))	echo("hello")	echo(struct{}{})	echo(time.Now())}

    反编译后代码非常长,精简如下。编译的时候禁止优化和内联,否则实例化的代码内联后看不到效果了。

    可以看到函数echo编译成了不同的函数:"".echo[.shape.int]、"".echo[.shape.int32]、"".echo[.shape.uint32]、"".echo[.shape.uint64]、"".echo[.shape.string]、"".echo[.shape.struct{}]、"".echo[.shape.struct{ time.wall uint64; time.ext int64; time.loc *time.Location }]不同的函数,即使shape一样的类型(int32、uint32)。 调用这些函数时,是通过""..dict.echo[uint64]这种方式调用的。

    所以我谨慎怀疑,Go的泛型方式在逐步的向第三种方案进化。

    123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121
    # command-line-arguments"".Test STEXT size=185 args=0x0 locals=0x48 funcid=0x0	0x0000 00000 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:12)	TEXT	"".Test(SB), ABIInternal, $72-0	"".Test STEXT size=185 args=0x0 locals=0x48 funcid=0x0	0x0000 00000 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:12)	TEXT	"".Test(SB), ABIInternal, $72-0	0x0000 00000 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:12)	CMPQ	SP, 16(R14)	0x0004 00004 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:12)	PCDATA	$0, $-2	0x0004 00004 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:12)	JLS	175	0x000a 00010 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:12)	PCDATA	$0, $-1	0x000a 00010 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:12)	SUBQ	$72, SP	0x000e 00014 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:12)	MOVQ	BP, 64(SP)	0x0013 00019 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:12)	LEAQ	64(SP), BP	0x0018 00024 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:12)	FUNCDATA	$0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)	0x0018 00024 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:12)	FUNCDATA	$1, gclocals·54241e171da8af6ae173d69da0236748(SB)	0x0018 00024 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:13)	LEAQ	""..dict.echo[int](SB), AX	0x001f 00031 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:13)	XORL	BX, BX	0x0021 00033 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:13)	PCDATA	$1, $0	0x0021 00033 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:13)	CALL	"".echo[.shape.int](SB)	0x0026 00038 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:14)	LEAQ	""..dict.echo[int32](SB), AX	0x002d 00045 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:14)	XORL	BX, BX	0x002f 00047 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:14)	CALL	"".echo[.shape.int32](SB)	0x0034 00052 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:15)	LEAQ	""..dict.echo[uint32](SB), AX	0x003b 00059 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:15)	XORL	BX, BX	0x003d 00061 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:15)	NOP	0x0040 00064 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:15)	CALL	"".echo[.shape.uint32](SB)	0x0045 00069 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:16)	LEAQ	""..dict.echo[uint64](SB), AX	0x004c 00076 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:16)	XORL	BX, BX	0x004e 00078 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:16)	CALL	"".echo[.shape.uint64](SB)	0x0053 00083 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:17)	LEAQ	""..dict.echo[string](SB), AX	0x005a 00090 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:17)	LEAQ	go.string."hello"(SB), BX	0x0061 00097 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:17)	MOVL	$5, CX	0x0066 00102 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:17)	CALL	"".echo[.shape.string](SB)	0x006b 00107 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:18)	LEAQ	""..dict.echo[struct{}](SB), AX	0x0072 00114 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:18)	CALL	"".echo[.shape.struct{}](SB)	0x0077 00119 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:19)	CALL	time.Now(SB)	0x007c 00124 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:19)	MOVQ	AX, ""..autotmp_0+40(SP)	0x0081 00129 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:19)	MOVQ	BX, ""..autotmp_0+48(SP)	0x0086 00134 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:19)	MOVQ	CX, ""..autotmp_0+56(SP)	0x008b 00139 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:19)	MOVQ	CX, DI	0x008e 00142 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:19)	MOVQ	BX, CX	0x0091 00145 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:19)	MOVQ	AX, BX	0x0094 00148 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:19)	LEAQ	""..dict.echo[time.Time](SB), AX	0x009b 00155 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:19)	NOP	0x00a0 00160 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:19)	CALL	"".echo[.shape.struct{ time.wall uint64; time.ext int64; time.loc *time.Location }](SB)	0x00a5 00165 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:20)	MOVQ	64(SP), BP	0x00aa 00170 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:20)	ADDQ	$72, SP	0x00ae 00174 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:20)	RET	0x00af 00175 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:20)	NOP	0x00af 00175 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:12)	PCDATA	$1, $-1	0x00af 00175 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:12)	PCDATA	$0, $-2	0x00af 00175 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:12)	CALL	runtime.morestack_noctxt(SB)	0x00b4 00180 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:12)	PCDATA	$0, $-1	0x00b4 00180 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:12)	JMP	0    ................."".echo[.shape.int] STEXT dupok size=268 args=0x10 locals=0x88 funcid=0x0	0x0000 00000 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:8)	TEXT	"".echo[.shape.int](SB), DUPOK|ABIInternal, $136-16	.................	0x00c2 00194 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:9)	MOVQ	DI, SI	0x00c5 00197 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:9)	PCDATA	$1, $0	0x00c5 00197 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:9)	CALL	fmt.Sprintf(SB)    ................."".echo[.shape.int32] STEXT dupok size=266 args=0x10 locals=0x88 funcid=0x0	0x0000 00000 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:8)	TEXT	"".echo[.shape.int32](SB), DUPOK|ABIInternal, $136-16	.................	0x00bd 00189 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:9)	MOVL	$1, DI	0x00c2 00194 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:9)	MOVQ	DI, SI	0x00c5 00197 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:9)	PCDATA	$1, $0	0x00c5 00197 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:9)	CALL	fmt.Sprintf(SB)    ................."".echo[.shape.uint32] STEXT dupok size=266 args=0x10 locals=0x88 funcid=0x0	0x0000 00000 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:8)	TEXT	"".echo[.shape.uint32](SB), DUPOK|ABIInternal, $136-16	.................	0x00c5 00197 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:9)	PCDATA	$1, $0	0x00c5 00197 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:9)	CALL	fmt.Sprintf(SB)    ................."".echo[.shape.uint64] STEXT dupok size=268 args=0x10 locals=0x88 funcid=0x0	0x0000 00000 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:8)	TEXT	"".echo[.shape.uint64](SB), DUPOK|ABIInternal, $136-16	.................	0x00c2 00194 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:9)	MOVQ	DI, SI	0x00c5 00197 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:9)	PCDATA	$1, $0	0x00c5 00197 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:9)	CALL	fmt.Sprintf(SB)    ................."".echo[.shape.string] STEXT dupok size=295 args=0x18 locals=0x88 funcid=0x0	0x0000 00000 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:8)	TEXT	"".echo[.shape.string](SB), DUPOK|ABIInternal, $136-24	.................	0x00d6 00214 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:9)	PCDATA	$1, $2	0x00d6 00214 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:9)	CALL	fmt.Sprintf(SB)    ................."".echo[.shape.struct{}] STEXT dupok size=208 args=0x8 locals=0x88 funcid=0x0	0x0000 00000 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:8)	TEXT	"".echo[.shape.struct{}](SB), DUPOK|ABIInternal, $136-8	.................	0x0093 00147 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:9)	PCDATA	$1, $0	0x0093 00147 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:9)	CALL	fmt.Sprintf(SB)    .................	0x00cb 00203 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:8)	JMP	0	................."".echo[.shape.struct{ time.wall uint64; time.ext int64; time.loc *time.Location }] STEXT dupok size=364 args=0x20 locals=0xa0 funcid=0x0	0x0000 00000 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:8)	TEXT	"".echo[.shape.struct{ time.wall uint64; time.ext int64; time.loc *time.Location }](SB), DUPOK|ABIInternal, $160-32	.................	0x00c5 00197 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:9)	CMPL	runtime.writeBarrier(SB), $0	0x00cc 00204 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:9)	JEQ	208	0x00ce 00206 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:9)	JMP	214	0x00d0 00208 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:9)	MOVQ	AX, 8(CX)	0x00d4 00212 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:9)	JMP	221	0x00d6 00214 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:9)	CALL	runtime.gcWriteBarrier(SB)	.................	0x0167 00359 (/Users/chaoyuepan/go/src/github.com/smallnest/study/type_parameter/generic/generic.go:8)	JMP	0	..............."".echo[.shape.string].stkobj SRODATA static size=32	......."".echo[.shape.string].arginfo1 SRODATA static dupok size=9	.......           .........."".echo[.shape.struct{}].stkobj SRODATA static size=32	......."".echo[.shape.struct{}].arginfo1 SRODATA static dupok size=5	......."".echo[.shape.struct{ time.wall uint64; time.ext int64; time.loc *time.Location }].stkobj SRODATA static size=56	......"".echo[.shape.struct{ time.wall uint64; time.ext int64; time.loc *time.Location }].arginfo1 SRODATA static dupok size=11	0x0000 00 08 fe 08 08 10 08 18 08 fd ff                 ...........

    泛型的性能

    写一个简单的benchmark程序,没看到明显的性能变化。

    12345678910111213141516171819202122232425262728293031323334353637
    package bench_testimport (	"fmt"    "testing")func BenchmarkAdd_Generic(b *testing.B) {	for i := 0; i < b.N; i++ {		add(i, i)	}}func BenchmarkAdd_NonGeneric(b *testing.B) {	for i := 0; i < b.N; i++ {		addInt(i, i)	}}type Addable interface {	int}func add[T Addable](a, b T) T {	return a + b}func addInt(a, b int) int {	return a + b}func main() {	fmt.Println(add(1, 2))	fmt.Println(addInt(1, 2))}

    参考文档

    1. https://github.com/golang/proposal/blob/master/design/generics-implementation-dictionaries.md
    2. https://github.com/golang/proposal/blob/master/design/generics-implementation-gcshape.md
    3. https://github.com/golang/proposal/blob/master/design/generics-implementation-stenciling.md
    4. https://github.com/golang/proposal/blob/master/design/43651-type-parameters.md
    5. https://docs.google.com/document/d/1vrAy9gMpMoS3uaVphB32uVXX4pi-HnNjkMEgyAHX4N4/view#
    ]]>
    2