[daily][optimize] 去吃面 (python类型转换函数引申的性能优化)(未完待续)
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[daily][optimize] 去吃面 (python类型转换函数引申的性能优化)(未完待续)相关的知识,希望对你有一定的参考价值。
前天,20161012,到望京面试。第四个职位,终于进了二面。好么,结果人力安排完了面试时间竟然没有通知我,也没有收到短信邀请。如果没有短信邀请门口的保安大哥是不让我进去大厦的。然后,我在11号接到了面试官直接打来的电话,问我为啥还没到,我说没人通知我我不知道呀。结果我就直接被他邀请去以访客的身份参加面试了。不知道人力的姑娘是不是认识我,且和我有仇,终于可以报复了。。。
然后,我终于如约到了,面试官带着我去前台登记。前台的妹子更萌。。。认为我是面试官,面试官是才是来面试的。我气质真的那么合吗?
当与前台的姑娘交谈,你就会直面的感受到他们刻意用心经营的企业文化。仅称呼就让我起了一身的鸡皮疙瘩,好想转身逃掉。可是,永远不要与钱为敌。
言归正传,吃面正式开始。两个面试官,一个不修边幅放眼望去就是功力深厚的技术人员,手长的纤细柔软而冰冷,握起来有气无力。但是总有一种随时可以变身成浩克的光环莫名萦绕。另一位年纪轻轻,估计定是某顶尖高校的顶尖生,专业知识想必烂熟于心。坐下的时候,两人默默,专心的浏览简历。于是我先打破沉默,主动做了自我介绍,还没到一半的时候被高才生打断,告诉我光说不清晰画一画。我便自己起身找了找那面墙是用来写字的,又到处找了找白板笔放在哪里,然后开画。边画边讲,还没到一半,我看了一眼,发现高才生在电脑上忙叨,也不知忙叨什么。浩克大哥低着头暗自神伤。两个人都完全没有在听,看来早已失去了兴趣。可能是我为了提高成功率表现的太能说会道了,反而不像个技术人员,倒像个产品销售之类的,这一点确实会让一个纯粹的技术人员产生不信任感和不安全感。
然后我终于把一段在别人家讲起来生动有趣,大部分人都会与我彼此讨论差不多一个小时的架构设计。对着两个心不在焉的人草草简短结束。现在回想起来我还没有弄明白,到底是那一个点让他们如此之快的失去了兴趣。衣服还没脱就已经萎了。。。然后,他们决定开始第二部分。编程测试。给了我一个macbook,开了个console。
好,相声讲完了,下面言归正传。
题如下:
一个60万行的文本文件,每行有逗号分隔的12列。第一列为字符串(为任意字符),后十一列为数组。
写一个python程序,将后11列的数字按列相加,将11个加和的数字按顺序打印,如,第一行的第二列加第二行的第二列加第三行的第二列。。。。
我复制了一个统一样本输出的程序 generator.py ,用以测试如下:
#! /usr/bin/python2 import random import time def main(): maxv = 1<<16 - 1 f = open(‘base.txt‘, ‘w‘) random.seed(1476599333) for i in range(0, 600000): length = random.randint(10, 50) a = "" for i in range(0, length): v = random.randint(32, 126) a += chr(v) for i in range(0, 11): v = random.randint(0, maxv) a += ‘,‘ a += str(v) a += ‘\n‘ f.write(a) f.close() if ‘__main__‘ == __name__: main()
使用上述程序,可以得到一个原始输入文件,用于测试。
我的第一份答案是这样的:
#! /usr/bin/python2 def main(): f = open(‘base.txt‘, ‘r‘) result = [‘HOST‘, 0,0,0,0,0,0,0,0,0,0,0] for line in f.readlines(): l = line.strip().split(‘,‘) length = len(l) if length < 12 : raise Exception, "HUGE_TONG: Wrong format" skip = length - 12 for i in range(1,12): result[i] += int(l[i+skip]) f.close() print result if __name__ == ‘__main__‘: r = main() exit(r)
运行结果:
[[email protected] chimian]$ time ./run.py [‘HOST‘, 9829498952, 9833094366, 9827153757, 9835131709, 9843502266, 9836377986, 9825044768, 9833232152, 9841073437, 9833009489, 9833147503] real 0m3.787s user 0m3.747s sys 0m0.037s
共花了 3.7 秒,顶尖生说这实在太慢了,要优化! 优化个毛线啊,我巴拉巴拉随便说了点思路,他说不对,我就投降了,因为性能不满意就用C好了!从来没想过python还要玩这么高深的优化,其实对于这种狗屁面试题我很生气。他说耗性能的是int()函数,可以用generator什么什么之类的。回来之后和张老师以及胡老师发了发牢骚,他们还挺兴趣的这个题。索性我也就再重新好好研究一下。
首先,优化的第一步是找到性能瓶颈。如高才生所言,我把int()函数替换成一般的加法,输出结果如下:
[[email protected] chimian]$ time ./run-md.py [‘HOST‘, 600000, 600000, 600000, 600000, 600000, 600000, 600000, 600000, 600000, 600000, 600000] real 0m1.208s user 0m1.180s sys 0m0.023s [[email protected] chimian]$ diff run.py run-md.py 13c13,14 < result[i] += int(l[i+skip]) --- > # result[i] += int(l[i+skip]) > result[i] += 1 [[email protected] chimian]$
在未知性能瓶颈的情况下,我们首先需要找到程序中最耗时的部分所在,一般在C语言中,我一般使用工具grof。那么python怎么办?
读了官方文档,关于调试与性能优化的章节 但是输出只能查看到用户逻辑部分,即使使用了高级功能,并不能看见python内部调用的统计,也不能查看逐行的统计,当然或许时因为我没看太懂所以不会。
>>> c=cProfile.Profile() >>> c.enable() >>> run.main() [‘HOST‘, 9829498952, 9833094366, 9827153757, 9835131709, 9843502266, 9836377986, 9825044768, 9833232152, 9841073437, 9833009489, 9833147503] >>> c.disable() >>> ps = pstats.Stats(c) >>> ps.sort_stats(‘cumulative‘).print_stats() 2400010 function calls in 4.283 seconds Ordered by: cumulative time ncalls tottime percall cumtime percall filename:lineno(function) 1 0.000 0.000 4.283 4.283 <stdin>:1(<module>) 1 3.669 3.669 4.283 4.283 run.py:3(main) 600000 0.318 0.000 0.318 0.000 {method ‘split‘ of ‘str‘ objects} 600000 0.140 0.000 0.140 0.000 {range} 600000 0.069 0.000 0.069 0.000 {method ‘strip‘ of ‘str‘ objects} 1 0.053 0.053 0.053 0.053 {method ‘readlines‘ of ‘file‘ objects} 600000 0.033 0.000 0.033 0.000 {len} 2 0.000 0.000 0.000 0.000 /usr/lib/python2.7/encodings/utf_8.py:15(decode) 2 0.000 0.000 0.000 0.000 {_codecs.utf_8_decode} 1 0.000 0.000 0.000 0.000 {open} 1 0.000 0.000 0.000 0.000 {method ‘close‘ of ‘file‘ objects} 1 0.000 0.000 0.000 0.000 {method ‘disable‘ of ‘_lsprof.Profiler‘ objects} <pstats.Stats instance at 0x7f6ef2c8e6c8> >>>
可以看到,根据cprofile模块的统计,耗时最长的是split()函数。
求助google,发现了 line_profiler 可以做行统计
[[email protected] ~]$ sudo pip2 install line_profiler
用法:
1. 在需要分析的函数前增加修饰行 @profile
@profile def main(): f = open(‘base.txt‘, ‘r‘)
2. 使用如下命令
[[email protected] chimian]$ kernprof -v -l run.py [‘HOST‘, 9829498952, 9833094366, 9827153757, 9835131709, 9843502266, 9836377986, 9825044768, 9833232152, 9841073437, 9833009489, 9833147503] Wrote profile results to run.py.lprof Timer unit: 1e-06 s Total time: 10.9936 s File: run.py Function: main at line 3 Line # Hits Time Per Hit % Time Line Contents ============================================================== 3 @profile 4 def main(): 5 1 8 8.0 0.0 f = open(‘base.txt‘, ‘r‘) 6 1 1 1.0 0.0 result = [‘HOST‘, 0,0,0,0,0,0,0,0,0,0,0] 7 600001 315209 0.5 2.9 for line in f.readlines(): 8 600000 723354 1.2 6.6 l = line.strip().split(‘,‘) 9 600000 273985 0.5 2.5 length = len(l) 10 600000 264674 0.4 2.4 if length < 12 : 11 raise Exception, "HUGE_TONG: Wrong format" 12 600000 263102 0.4 2.4 skip = length - 12 13 7200000 3191710 0.4 29.0 for i in range(1,12): 14 6600000 5961474 0.9 54.2 result[i] += int(l[i+skip]) 15 1 14 14.0 0.0 f.close() 16 1 37 37.0 0.0 print result
可以看出,第14行占用了54%的运行时间,由于该行符合了多条运算,我门,试着将其展开在进行查看。
[[email protected] chimian]$ kernprof -v -l run.py [‘HOST‘, 9829498952, 9833094366, 9827153757, 9835131709, 9843502266, 9836377986, 9825044768, 9833232152, 9841073437, 9833009489, 9833147503] Wrote profile results to run.py.lprof Timer unit: 1e-06 s Total time: 20.4831 s File: run.py Function: main at line 3 Line # Hits Time Per Hit % Time Line Contents ============================================================== 3 @profile 4 def main(): 5 1 7 7.0 0.0 f = open(‘base.txt‘, ‘r‘) 6 1 1 1.0 0.0 result = [‘HOST‘, 0,0,0,0,0,0,0,0,0,0,0] 7 600001 343923 0.6 1.7 for line in f.readlines(): 8 600000 772508 1.3 3.8 l = line.strip().split(‘,‘) 9 600000 305425 0.5 1.5 length = len(l) 10 600000 281454 0.5 1.4 if length < 12 : 11 raise Exception, "HUGE_TONG: Wrong format" 12 600000 283245 0.5 1.4 skip = length - 12 13 7200000 3414854 0.5 16.7 for i in range(1,12): 14 # result[i] += int(l[i+skip]) 15 6600000 3044661 0.5 14.9 index = i + skip 16 6600000 3001742 0.5 14.7 value_str = l[index] 17 6600000 5589397 0.8 27.3 value_int = int(value_str) 18 6600000 3445814 0.5 16.8 result[i] += value_int 19 1 13 13.0 0.0 f.close() 20 1 37 37.0 0.0 print result
实现了一个C语言版的用于对比:
#include <stdio.h> #include <stdlib.h> #include <errno.h> #include <string.h> int main() { FILE* fp = fopen("./base.txt", "r"); char* buf = malloc(1024); size_t len = 0; int slen = 0; long array[11] = {0,0,0,0,0,0,0,0,0,0,0}; while (1) { int r = getline(&buf, &len, fp); if (r < 0) { if (errno != 0) perror("HUGE_TONG read(): "); break; } slen = strlen(buf); if (buf[slen-1] != ‘\n‘) { printf("do not supporting format: %c \n", buf[slen-1]); } char* str; buf[slen-1] = 0; int index = 10; for (int i = slen - 1; i >= 0; i--) { if (buf[i] == ‘,‘) { str = buf + (i+1); buf[i] = 0; int value = atoi(str); array[index--] += value; if (index < 0) { break; } } } } free(buf); fclose(fp); for (int i = 0; i < 11; i++) { printf("%ld\t", array[i]); } printf("\n"); return 0; }
用时0.28秒,相差10倍之多。
[[email protected] chimian]$ gcc -Wall -O3 crun.c [[email protected] chimian]$ time ./a.out 9829498952 9833094366 9827153757 9835131709 9843502266 9836377986 9825044768 9833232152 9841073437 9833009489 9833147503 real 0m0.288s user 0m0.277s sys 0m0.010s
尝试将py编译,后执行,效率无任何变化
[[email protected] chimian]$ python2 -O3 -m py_compile run.py [[email protected] chimian]$ time python2 run.pyo [‘HOST‘, 9829498952, 9833094366, 9827153757, 9835131709, 9843502266, 9836377986, 9825044768, 9833232152, 9841073437, 9833009489, 9833147503] real 0m3.865s user 0m3.833s sys 0m0.030s
如前文,再次将类型转换函数替换为普通的赋值语句
[[email protected] chimian]$ diff run.py run-md.py 17c17 < value_int = int(value_str) --- > value_int = 23298 [[email protected] chimian]$ vim run-md.py [[email protected] chimian]$ vim run-md.py [[email protected] chimian]$ time ./run-md.py [‘HOST‘, 13978800000, 13978800000, 13978800000, 13978800000, 13978800000, 13978800000, 13978800000, 13978800000, 13978800000, 13978800000, 13978800000] real 0m1.449s user 0m1.420s sys 0m0.027s [[email protected] chimian]$ vim run-md.py [[email protected] chimian]$ kernprof -v -l run-md.py [‘HOST‘, 13978800000, 13978800000, 13978800000, 13978800000, 13978800000, 13978800000, 13978800000, 13978800000, 13978800000, 13978800000, 13978800000] Wrote profile results to run-md.py.lprof Timer unit: 1e-06 s Total time: 17.2632 s File: run-md.py Function: main at line 3 Line # Hits Time Per Hit % Time Line Contents ============================================================== 3 @profile 4 def main(): 5 1 7 7.0 0.0 f = open(‘base.txt‘, ‘r‘) 6 1 1 1.0 0.0 result = [‘HOST‘, 0,0,0,0,0,0,0,0,0,0,0] 7 600001 335628 0.6 1.9 for line in f.readlines(): 8 600000 728998 1.2 4.2 l = line.strip().split(‘,‘) 9 600000 293883 0.5 1.7 length = len(l) 10 600000 278300 0.5 1.6 if length < 12 : 11 raise Exception, "HUGE_TONG: Wrong format" 12 600000 276842 0.5 1.6 skip = length - 12 13 7200000 3302175 0.5 19.1 for i in range(1,12): 14 # result[i] += int(l[i+skip]) 15 6600000 2965126 0.4 17.2 index = i + skip 16 6600000 2943092 0.4 17.0 value_str = l[index] 17 6600000 2808623 0.4 16.3 value_int = 23298 18 6600000 3330465 0.5 19.3 result[i] += value_int 19 1 12 12.0 0.0 f.close() 20 1 39 39.0 0.0 print result
再测,将内层for循环的操作,只保留为一次加赋值操作。
[[email protected] chimian]$ time ./run-md.py [‘HOST‘, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] real 0m0.960s user 0m0.923s sys 0m0.033s [[email protected] chimian]$ cat run-md.py #! /usr/bin/python2 #@profile def main(): f = open(‘base.txt‘, ‘r‘) result = [‘HOST‘, 0,0,0,0,0,0,0,0,0,0,0] for line in f.readlines(): l = line.strip().split(‘,‘) length = len(l) if length < 12 : raise Exception, "HUGE_TONG: Wrong format" skip = length - 12 a = 0 for i in range(1,12): # result[i] += int(l[i+skip]) # index = i + skip # value_str = l[index] # value_int = 23298 # result[i] += value_int # result[i] += 23298 a += 23298 f.close() print result if __name__ == ‘__main__‘: r = main() # exit(r) [[email protected] chimian]$
再测,仅做6600000次加赋值操作
[[email protected] chimian]$ time ./run-test.py real 0m0.372s user 0m0.293s sys 0m0.077s [[email protected] chimian]$ cat run-test.py #! /usr/bin/python2 def main(): a = 0; for i in range(0, 6600000): a += 235678 if __name__ == ‘__main__‘: main() [[email protected] chimian]$
并没有太多的思路,查到一篇 文章,写的很好。
看完这篇文章,我觉得我被他们耍了;这是一个典型的python特有的优化问题,根本没有跨语言优化的意义。C语言职位,考这个。。。
未完待续。。。
以上是关于[daily][optimize] 去吃面 (python类型转换函数引申的性能优化)(未完待续)的主要内容,如果未能解决你的问题,请参考以下文章