系统性能卡又慢?这十大性能错误你都检查了吗?

系统性能对于生产环境、工作效率的影响不言而喻。如果你的系统性能又卡又慢,也许你需要检索一下这十大性能错误你中了哪条了。别担心,规避的建议也在这儿呢!

Martin Thompson是LMAX的联合创始人,在QCon圣保罗2016上做过关于性能的keynote演讲,他列出了在生产环境中所遇到的十大性能相关的错误。

Martin Thompson是LMAX的联合创始人,在QCon圣保罗2016上做过关于性能的keynote演讲。他最初计划的演讲题目为“关于性能的神话与传说”,不过Thompson后来将演讲命名为“十大性能错误”,因为“我们都会犯错误,而且很容易就会出现错误”。

下面列出了他在生产环境下所见到的十大性能错误,并且还包含了如何避免的建议。

10、不进行升级

很多人抱怨他们的系统不够快,并通过编写更好的算法和数据结构来寻求帮助,Thompson认为实际上“他们所需的仅仅就是进行升级”。升级操作系统、JVM、CLR等等。不进行升级的常见借口就是“在新版本中可能会有bug。”

为了避免这种状况,可以进行定期的持续集成和测试,这应该是开发流程的基础组成部分。Thompson以一个实时系统进行了例证,开发人员针对新版本的数据库进行了测试,在所有的测试通过之后,他们就将其发布到了生产环境之中。

9、重复性的工作

Thompson讲述了某个系统的故事,这个系统是用来提供Web页面的,它非常缓慢,开发人员最初认为是数据库的问题并试图在这方面进行调优。但是当他在系统上运行profiler时,发现在一个循环中,ORM被调用了7,000次,这才是页面加载缓慢的罪魁祸首。当这个循环的问题修复之后,系统的响应变得完全正常。这里学到的经验就是“对系统进行度量。如果系统是一个黑盒的话,你就无法说明时间都耗费在了哪里。”

8、加载性能依赖于数据

Thompson展现了一个基准测试结果,它会执行一项操作,该操作会对内存(RAM)中1GB数组的所有long型进行求和。这里所耗费的时间取决于内存是如何访问的,如下面的表格所示:

加载性能

这个基准测试的结果显示并非所有的内存操作都是等价的,我们需要关注它是如何进行处理的。Thompson认为非常重要的一点在于了解各种数据结构的性能,他指出对于2GB以上的场景,Java的HashMap要比.NET的Dictionary慢十倍以上。他还补充说,也有一些场景.NET要比Java慢得多。

7、分配的内存太多

尽管在很多场景中,内存分配几乎是没有什么成本的,但是它们的回收却并非如此,因为在面对大量的数据集时,垃圾收集器需要更多的时间。当分配大量的数据时,缓存会被填满,较旧的数据会被舍弃,使得在数据操作上的效率变为90ns/op而不是7ns/op,这里变慢了不止一个数量级。

6、采用并行

尽管对于特定的算法来说,采用并行很有吸引力,但是它也有一些局限性和相关的开销。Thompson引用了“可扩展性!但是其COST如何?”这篇论文,论文的作者通过引入COST(胜过单线程的配置,Configuration that Outperforms a Single Thread)对比了并行系统以及单线程的系统,COST的定义如下:

在特定的平台中,特定问题的COST指的是优于单线程方案所需的硬件配置。COST将系统的扩展性与系统所引入的开销进行了权衡,并指明了系统实际所能取得的性能,它们可能并没有带来实际的收益,却增加了并行所引入了开销。

作者分析了各种数据并行系统的测量结果,并得出如下的结论:“很多的系统要么具有非常高的COST,通常会需要上百个核心,要么针对他们所报告的配置,其性能要比单线程方案更差。”

在这个话题中,Thompson指出,并行任务会有相关的通信和同步开销,并且有些活动本质上要求是串行的,不能实现并行。按照Amdahl定律,如果系统中有5%的活动需要串行,那么不管使用了多少个处理器,系统的速度提升最多只能达到20倍。

Thompson还提到了Neil J. Gunther在1993年所提出的通用可扩展性定律(Universal Scalability Law,PDF),该定律指出在并行非共享系统(shared-nothing)中甚至会存在更多的局限性,当所使用的处理器数量达到一定程度后,速度会出现下降,这取决于并发、竞争以及同步的水平。(更多的细节可以参考如何量化可扩展性这个页面。)按照上述两个规律所总结的速度与处理器数量之间的关系如下图所示:

处理器数量

Thompson指出通过USL能够看到性能的下降,这要归因于并行系统中组件之间进行通信所消耗的成本:“在系统中,所投入资源越多,通信路径也会随之增多,这会使算法的效率降低。”

Thompson补充说,在构建并行系统时,主要的建议是避免共享可变(mutable)的状态,因为“它非常难以进行判断……最终你会遇到很多的bug”。推荐的方式是要么采用非共享架构,要么针对特定的一块数据,只使用一个写入器。

对这个性能问题,他的最终建议:如果你想提升算法的速度的话,在尝试并行方案之前,先设法提升单线程版本的性能,因为并行方案实在是太难了。

5、不理解TCP

针对这个话题,Thompson认为很多在考虑微服务架构的人对TCP并没有充分的理解。在特定的场景中,有可能会遇到延迟的ACK,它会限制链路上所发送的数据包,每秒钟只会有2-5个数据包。这是因为TCP两个算法所引起的死锁:Nagle以及TCP Delayed Acknowledgement。在200-500ms的超时之后,会打破这个死锁,但是微服务之间的通信却会分别受到影响。推荐的方案是使用TCP_NODELAY,它会禁用Nagle的算法,多个更小的包可以依次发送。按照Thompson的说法,其中的差别在5到500 req/sec。

4、同步通信

客户端和服务器之间的同步通信会带来时间的损耗,对于需要快速通信的系统来说,这会成为一个问题。Thompson说,它的解决方案并不是购买更加昂贵和快速的硬件,而是使用异步通信。在这种场景下,客户端可以发送多个请求到服务器端,而不必等待它们之间的响应。采用这种方式需要改变客户端发送请求的方式,但这是值得的。

3、文本编码

开发人员很多时候会选择使用文本编码格式实现链路上的数据传输,比如JSON、XML或Base64,因为“这对人类是可读的”。但是Thompson指出在两个系统之间进行对话的时候,是没有人读这些数据的。借助这种方式,使用简单的文本编辑器就能很容易地进行调试,但是在将二进制数据与文本之间进行互相转换的时候,这会带来很高的CPU损耗。该问题的解决方案是使用能够理解二进制的更好的工具,Thompson提到了Wireshark。

2、API设计

按照Thompson的说法,有一些与性能相关的最负面影响是由API引起的。它使用如下的代码来阐述较差的代码签名:

public void startElement( String uri, String localName, String qName, Attributes atts) throws SAXException

描述:

在处理XML的时候,通常我们并不会使用这些值[三个String以及属性的集合]。我们分配了很多的内容,但是却将其浪费并抛弃掉了。这会损耗电池的寿命,白白地浪费资源。我们需要使其更加简单一些。

他建议采用如下的签名,实现更加简单的方法:

public void characters( char[] ch, int start, int length) throws SAXException

有些人可能会抱怨后面的这个方法要比前一个更难用,Thompson建议采用组合的方式,将其中一个用另一个封装起来,这样的话,能够给用户多一个选择。如果性能不是什么问题的话,可以采用第一种(使用String),否则的话,第二个方案会更好一些。

Thompson提到的第二个样例是字符串拆分:

public String[] split(String regex)

这个方法签名相关的性能问题包括:

  • 每次方法调用的时候,正则表达式都需要进行编译;
  • 需要实例化一个动态的结构,用来存储字符串中所包含的初始数量未知的token;
  • 返回的结构是一个固定大小的数组,这就必须要将token收集到一个临时的结构中,然后再拷贝到数组里面;
  • 如果调用者想要对这些token进行一些操作的话,比如排序,需要将它们拷贝到另外一个结构之中。

更好的方案是使用Iterable,它能够避免在内存中创建中间状态的token副本:

public Iterable split(String regex)

另外一种方案是允许调用者提供存储token的集合。如果调用者想要对token列表去重的话,应该传递一个Set进来,如果想得到有序列表的话,就需要传递一个TreeMap进来:

public void split( String regex, Collection dst)

1、日志

Thompson所列的排名第一的性能问题是写日志所耗费的时间。他通过一个图表展现了当线程数增加的时候,日志操作所耗费的平均时间:

日志

这个图显示了一个100%的顺序操作,不管使用多少线程来记录日志,所需的时间均呈线性增长。Thompson说大多数已有的日志系统都可以得出这样一幅图表,“Logger是系统中最大的瓶颈之一”。这个问题的解决方案是使用异步的Logger。

另外,Logger所记录的数据应该是结构化的数据,便于后续的工具进行读取和处理,而不应该是一堆String。如果是记录重复的错误,他建议在错误第一次出现的时候进行记录,后续出现时只需对一个计数器进行递增,告知对应的错误出现了多少次即可。对于实时系统的调试,Thompson建议使用代码编织(code weaver)的技术,如Byte Buddy,因为它能够避免编写和运行不必要的日志代码。

本文翻译已获授权,原文链接:

https://www.infoq.com/articles/top-10-performance-mistakes

本文译者:张卫滨

文章出处:InfoQ

阅读原文

使用Python构建SQLite轻量级数据库

Python

Python

相信在童年时代看过《七龙珠》的小伙伴们都会记得《贝吉塔和那巴》卷里孙悟空和贝吉塔的那场世纪大战。拉迪兹死后,贝吉塔和那巴入侵地球。孙悟空在界王那里修炼完成后,赶回地球,与贝吉塔展开了一场鏖战。其中,悟空使出了三倍界王拳和四倍界王拳的冲击波把贝吉塔打成个猪头。这迫使贝吉塔造出一个小型轻量级的圆月,变身成了巨猿……

言归正传,今天让我来介绍一款小巧且强大的数据库构建工具。它就如同贝吉塔手中的那个小型轻量级的圆月那样,袖珍且实用。能够让使用者自己变成“巨猿”而释放出强大的能量。

它就是SQLite

Python

SQLite是一个开源的关系数据库,支持SQL语言。其具有零配置(zero configuration)、自我包含(self-contained)和便于传输(easy transfer)等优点,即高度便携、使用方便、结构紧凑、高效和可靠。

你可能不知道,你爱车的行车电脑系统里安装有数十个SQLite数据库;你的智能手机上(如iPhone)安装有上百甚至上千个SQLite数据库。如果你现在正在寻找一款能够用于管理科研和临床信息的数据库工具,而且你要求这款工具强大且简单易用,我认为SQLite是你的不二之选。

SQLite Browser

在介绍SQLite使用之前,首先介绍一款用于SQLite操作的软件,SQLite Browser(另一个名字叫DB Browser for SQLite)。它是一款图形界面的SQLite管理软件。

SQLite

SQLite的下载地址:http://sqlitebrowser.org/

下面就让我来简单介绍一下如何构建和维护一个简易的SQLite数据库。

一、SQL语言简介

我一直认为,除了Python语言外,最完美的计算机语言是SQL语言。它的描述功能简直太强大了。毫不夸张地说,你可以用接近英语语法的表达来描述任何对数据库的操作。

SQLite

让我来举例说明一下:

1、建立数据库

建立一个数据库,其字段分别为:患者姓名、住院号、诊断、基因型和所在医院,则SQL表述如下:

CREATE TABLE table_00( 患者姓名 VARCHAR(120), 住院号 INTERGAR(10000), 诊断 VARCHAR(120), CYP2C19基因型VARCHAR(120))

应用SQLite Browser浏览结果:

数据库

2、插入记录

填入一个患者的记录信息:

INSERT INTO table_00( 患者姓名, 住院号, 诊断, CYP2C19基因型, 医院) VALUES(‘张三’, ‘10000’, ‘急性下壁心肌梗死’, ‘CYP2C19*1/*3’, ‘中国医科大学附属盛京医院’ )

应用SQLite Browser浏览结果:

数据库

3、删除记录

DELETE FROM table_00 WHERE 患者姓名 = ‘张三’

则该条记录被删除。

二、应用Python来进行SQLite编程

Python的标准库中就有能够调用SQLite的模块:sqlite3,无需特殊安装。

下面我就用实际的代码来实现前面的SQL语句:

import sqlite3        # 导入sqlite3模块

# 连接数据库文件:

conn = sqlite3.connect(‘患者信息.sqlite’)

cur = conn.cursor()

# 开始执行SQL语句:

str = cur.execute(‘SELECT * FROM table_00’)

# 添加字段:

cur.execute(‘CREATE TABLE table_00( 患者姓名 VARCHAR(120), 住院号 INTERGAR(10000), 诊断 VARCHAR(120), CYP2C19基因型 VARCHAR(120))’)

# 添加记录:

cur.execute(‘INSERT INTO table_00( 患者姓名, 住院号, 诊断, CYP2C19基因型, 医院) VALUES(‘张三’, ‘10000’, ‘急性下壁心肌梗死’, ‘CYP2C19*1/*3’, ‘中国医科大学附属盛京医院’ )’)

# 保存更改信息,不要忘记:

conn.commit()

# 使用断开数据库连接,养成良好习惯:

cur.close()

上面两个简单的SQL语句就这样用Python实现了。相应的数据库文件也被建立和保存了。同样你也可以应用cur.execute函数来删除该记录。非常简单。

以上是一例简单的数据库建立、插入和删除的演示。

愿数据与你同在!

Python

作者简介:杨川,心血管内科医师,从事临床医学10年,介入心脏病学3年,生物信息学1年。目前专注于生物信息学和个体化及精准医疗的实践和研究工作。作者个人公众号ID:ChuanWorkstation

出处:编程派(公众号ID:codingpy)

 

阅读原文