importsys
f(int(sys.argv[1]))
接下来,我们将会看到,下载一个网页 vs 执行一个 Python 脚本!
提示:本题答案都小于1亿 :)
猜猜看:1秒钟可以完成的HTTP请求数
#!/usr/bin/env python
# 猜数字: 一秒钟的时间,我们可以从
# google.com 下载多少页面?
fromurllib2importurlopen
deff(NUMBER):
for_inxrange(NUMBER):
r = urlopen(“http://google.com”)
r.read()
importsys
f(int(sys.argv[1]))
准确答案:4
猜猜看:1秒钟执行循环次数
#!/bin/bash
# 猜数字: 在一秒内,我们可以启动多少次
# Python解释器?
NUMBER=$1
foriin$(seq$NUMBER);do
python -c”;
done
准确答案:77
启动程序本身就非常耗时,并不只有是Python是这样。如果我们只是运行/bin/true,那么1秒能做500次,所以看起来运行任何程序一般需要大约1毫秒时间。当然,下载网页的快慢很大程度上取决于网页大小、网络连接速度、以及服务器间的距离1毫秒是多少秒,今天我们并不会深入探讨网络性能(网络性能是一件非常有趣的事情)。一个从事高性能网络开发的朋友告诉我,一次网络往返可以做到250ns(!!!),但是要求计算机距离非常近,同时搭配豪华的硬件配置。对我们和Google来讲,耗时是它的一百万倍。在一个纳秒的时间,光只能传播一英尺,而谷歌的服务器远在250英尺以外的地方。
在一秒钟时间内,可以向硬盘写多少字节的数据?我们都知道向内存写数据更快些,但是快多少呢?下面的代码在一个装有SSD的硬盘上执行。
猜猜看:一秒钟写入多少字节
#!/usr/bin/env python
# 猜数字: 一秒的时间我们可以向一个文件写入
# 多少byt字节?
# 注意:我们确保所有数据在退出前都已经同步到硬盘
importtempfile
importos
CHUNK_SIZE = 1000000
s = “a” * CHUNK_SIZE
defcleanup(f,name):
f.flush()
os.fsync(f.fileno())
f.close()
try:
os.remove(name)
except:
pass
deff(NUMBER):
name = ‘./out’
f = open(name,’w’)
bytes_written = 0
whilebytes_written < NUMBER:
f.write(s)
bytes_written += CHUNK_SIZE
cleanup(f,name)
importsys
f(int(sys.argv[1]))
准确答案:342,000,000
猜猜看:一秒钟写入多少字节
#!/usr/bin/env python
# 猜数字: 在一秒钟内,我们能够向一个
# 内存中的字符串写入多少字节
importcStringIO
CHUNK_SIZE = 1000000
s = “a” * CHUNK_SIZE
deff(NUMBER):
output = cStringIO.StringIO()
bytes_written = 0
whilebytes_written < NUMBER:
output.write(s)
bytes_written += CHUNK_SIZE
importsys
f(int(sys.argv[1]))
准确答案:2,000,000,000
硬盘比内存要慢,即使你使用“较慢”的语言,比如Python,这种差别也是有影响的。如果你使用一个非常快速的硬盘(我的SSD已知的写入速度>500MB/s,可以称得上快)很多事情最终都受限于硬盘的速度。我们来看下一个例子!
文件时间到!有时候我执行一条 grep 命令处理大量数据,然后它就一直执行下去了,grep 在一秒内可以搜索多少字节的数据呢?
注意,当程序执行时营销引流,grep读入的数据已经全部读入内存。这让我们能够知道grep慢的原因,多少是因为搜索,多少是因为读取到硬盘。
列出文件同样耗时!在一秒钟内可以列出多少文件呢?
猜猜看:1秒能够搜索多少字节?
#!/bin/bash
# 猜数字: `grep`命令1秒能够搜索多少字节
# 注意: 数据已经在内存中
NUMBER=$1
cat /dev/zero | head -c$NUMBER | grepblah
exit0
准确答案:2,000,000,000
猜猜看:一秒能够列出多少文件?
#!/bin/bash
# Number to guess: `find`命令 一秒钟能够列出多少文件?
# 注意: 文件在文件系统缓存中。
find / -name’*’2> /dev/null | head -n$1 > /dev/null
准确答案:325,000
很好!现在我知道grep可以以2GB/s的速度搜索,所以,至少在这个例子中,我们程序的速度主要受限于硬盘速度而不是grep的速度。
序列化通常是一个很耗时的工作,尤其是当你需要反复的序列化/反序列化一份数据的时候,真的非常痛苦。这里有一些基准测试:解析 64K 的JSON文件,同样的数据用 msgpack 格式编码。
猜猜看:一秒钟循环次数
#!/usr/bin/env python
# 猜数字: 在一秒钟内,我们能够解析一个
# 64K 的 JSON 文件多少次?
importjson
withopen(‘./setup/protobuf/message.json’)asf:
message = f.read()
deff(NUMBER):
for_inxrange(NUMBER):
json.loads(message)
importsys
f(int(sys.argv[1]))
准确答案:449
猜猜看:一秒钟循环次数
#!/usr/bin/env python
# 猜数字: 在一秒钟内,我们能够解析一个
# 46K 的msgpack 数据多少次?
importmsgpack
withopen(‘./setup/protobuf/message.msgpack’)asf:
message = f.read()
deff(NUMBER):
for_inxrange(NUMBER):
msgpack.unpackb(message)
importsys
f(int(sys.argv[1]))
准确答案:4000
基本上任何一个谈论序列化的人都会提到 capnproto 可以进行即时的序列化。我们只是想让你明白,反序列化一个64K的数据需要花上1微妙(据我们所知,这是非常长的时间了),而且你选择的格式和库也会带来非常大的影响。
数据库。我们并没有为你准备炫酷的PostgreSQL,取而代之的是我们弄了两份包含一千万行数据的SQLite数据表,一份设置了索引,另一份没有。
猜猜看:一秒钟执行的查询次数
#!/usr/bin/env python
# 猜猜看: 一秒钟的时间,我们可以从一
# 个包含一千万行数据,并设置了
# 索引的表中选取多少行
importsqlite3
conn = sqlite3.connect(‘./indexed_db.sqlite’)
c = conn.cursor()
deff(NUMBER):
query = “select * from my_table where key = %d” % 5
foriinxrange(NUMBER):
c.execute(query)
c.fetchall()
importsys
f(int(sys.argv[1]))
准确答案:53000
猜猜看:一秒钟执行的查询次数
#!/usr/bin/env python
# 猜猜看: 一秒钟的时间,我们可以从一
# 个包含一千万行数据,且没有设置
# 索引的表中选取多少行
importsqlite3
conn = sqlite3.connect(‘./unindexed_db.sqlite’)
c = conn.cursor()
deff(NUMBER):
query = “select * from my_table where key = %d” % 5
foriinxrange(NUMBER):
c.execute(query)
c.fetchall()
importsys
f(int(sys.argv[1]))
准确答案:2
并不出乎我们的意料:索引的效果很zan。20几微秒进行一次带索引查询意味着,如果这是基于一个距离很远的数据服务器的连接,那么查询的时间主要受限于到服务器的网络往返时间。
下面到哈希时间啦!在这里,我们将比较MD5(设计的初衷就是要速度快)和 bcrypt(设计的初衷就是要速度慢)。用MD5你在1秒时间内可以哈希到相当多的东西,而用 bcrypt 则不能。
猜猜看:一秒内可以哈希多少字节的数据
#!/usr/bin/env python
# 猜数字: 用MD5sum一秒内可以处理多少字节的数据
importhashlib
CHUNK_SIZE = 10000
s = ‘a’ * CHUNK_SIZE
deff(NUMBER):
bytes_hashed = 0
h = hashlib.md5()
whilebytes_hashed < NUMBER:
h.update(s)
bytes_hashed += CHUNK_SIZE
h.digest()
importsys
f(int(sys.argv[1]))
准确答案:455,000,000
猜猜看:一秒内可以哈希多少字节的密码
#!/usr/bin/env python
# 猜数字: 使用bcrypt一秒内可以哈希多少字节的密码
importbcrypt
password = ‘a’ * 100
deff(NUMBER):
for_inxrange(NUMBER):
bcrypt.hashpw(password,bcrypt.gensalt())
importsys
f(int(sys.argv[1]))
准确答案:3
接下来,让我们探讨一下内存访问。现在的 CPU 有 L1 和 L2 缓存,这比主内存访问速度更快。这意味着1毫秒是多少秒,循序访问内存(CPU可以把一大块数据加载进缓存)通常比不按顺序访问内存能提供更快的代码。
让我们看看,事实是多么令我们吃惊吧!你可能需要参考《Latency Numbers Every Programmer Should Know》来猜这一题。
猜猜看:一秒钟写的字节数
#include
#include
// 猜数字:一秒内我们可以分配
// 并填充一块多大的数组?
// 我们故意让它这么复杂,其实没有必要,这样才能和无序访问进行对比 :)
intmain(intargc,char **argv){
intNUMBER,i;
NUMBER = atoi(argv[1]);
char* array = malloc(NUMBER);
intj = 1;
for(i = 0;i < NUMBER; ++i){
j = j * 2;
if(j > NUMBER){
j = j – NUMBER;
}
array[i] = j;
}
printf(“%d”,array[NUMBER / 7]);
// so that -O2 doesn’t optimize out the loop
return0;
}
准确答案:376,000,000
猜猜看:一秒钟写的字节数
#include
#include
// 猜数字:一秒内我们可以分配
// 并填充一块多大的数组?
// 使用无序访问而不是有序访问
intmain(intargc,char **argv){
intNUMBER,i;
NUMBER = atoi(argv[1]);
char* array = malloc(NUMBER);
intj = 1;
for(i = 0;i < NUMBER; ++i){
j = j * 2;
if(j > NUMBER){
j = j – NUMBER;
}
array[j] = j;
}
printf(“%d”,array[NUMBER / 7]);
// so that -O2 doesn’t optimize out the loop
return0;
}
准确答案:68,000,000
我们通常不会写太多的C代码,所以并不会总是受其影响。但是如果你很在意你的命令耗时多少微秒的时候(当你尝试每秒处理十亿数据的时候,你就会在意),你就会很在意此类事情了。