上一篇发现线程池帮不了 CPU 密集的解析。进程池能帮多少、什么时候值得、要付出什么代价?这篇在本机 10 个核心上量:启动成本、不同页面大小下的加速比、传输页面的开销、返回值的限制,以及批量提交的影响。
创建 4 进程的池并跑完第一个空任务(冷启动):83 ms 池预热之后,一个空任务来回一趟:中位数 0.141 ms,p95 0.193 ms(300 次)
进程池冷启动(4 个进程)约 83 ms,预热之后每个任务来回要 0.141 ms。池要全局建一次反复用。
页面 个数 单页解析 直接 线程×8 进程×4 进程×8 进程×8 相对直接
6KB 4000 0.14 ms 468 ms 485 ms 123 ms 77 ms 6.06 倍
25KB 1500 0.47 ms 664 ms 595 ms 180 ms 107 ms 6.22 倍
125KB 300 2.25 ms 655 ms 559 ms 183 ms 112 ms 5.83 倍
635KB 60 11.27 ms 657 ms 557 ms 180 ms 118 ms 5.57 倍
2570KB 20 47.64 ms 915 ms 763 ms 253 ms 173 ms 5.30 倍在我的页面(一个表格页,解析结果只是数一数链接)上,8 个进程在 6 KB 到 2.5 MB 都快 5.3–6.2 倍,线程池基本不加速(0–1.2 倍)。这个收益依赖于每个任务都足够重、并且我批量提交了;核心数只有 10,所以 8 个进程接近用满。
5 KB:提交 + 返回一趟 0.137 ms;其中 pickle.dumps 本身 0.001 ms
100 KB:提交 + 返回一趟 0.154 ms;其中 pickle.dumps 本身 0.003 ms
2000 KB:提交 + 返回一趟 0.652 ms;其中 pickle.dumps 本身 0.044 ms
20000 KB:提交 + 返回一趟 9.482 ms;其中 pickle.dumps 本身 2.532 ms把页面传给子进程要序列化:2 MB 来回约 0.65 ms,20 MB 约 9.5 ms,相对解析的耗时(2.5 MB 页面 47.64 ms)是小头。
让子进程返回解析好的 lxml 树:TypeError: cannot pickle 'HtmlElement' object 返回一个整数(链接数 1077):3.03 ms;返回全部 1077 个链接字符串的列表:4.32 ms(列表序列化后约 19 KB)
子进程的返回值必须能 pickle,lxml 的元素树不行(TypeError: cannot pickle 'HtmlElement' object)。应该在子进程里就提取出需要的数据,只返回字符串、数字、列表之类。
直接解析:479 ms chunksize=1 :371 ms(相对直接解析 1.29 倍) chunksize=8 :78 ms(相对直接解析 6.15 倍) chunksize=125 :76 ms(相对直接解析 6.28 倍)
对 6 KB 这种小页面,解析一个只要 0.15 ms,和来回一趟的 0.14 ms 差不多。map(..., chunksize=1) 只快 1.29 倍,chunksize=8 以上才到 6 倍。任务越小,越要批量。