在处理数据集时,常常会遇到用for循环处理数据集的情况。但是如果简单地用for循环就会出现cpu资源利用不充分的情况。

下图是直接使用for循环的cpu利用率:大概是10-15之间,单线程无疑了,此外100个数据耗时50秒。

features = []
        with open(self.file_name) as f:
            for line in tqdm(f.readlines()[:100]):
                session = json.loads(line.strip())
                if config.early_preprocess:
                    feature = self.preprocess(session)
                    if feature is not None:
                        features.append(feature)
                else:
                    features.append(session)
        print(time()-start)

python PooledDB 多线程 python for 多线程_python

这时候我们可能就会想到多线程或者多进程。

但是可惜的是,由于python的特性(GIL锁),python的多线程只是优化了单个cpu核心的调度。

比如在爬取网站时,多线程允许该cpu核心无需等待被访问的服务器回应就能爬取其他网站。

换句话来说,python的多线程实际上还是只调用了单核心,只不过允许单核心同时处理多个线程而已,但因为单个核心的性能到底是有限的,所以python的多线程只适合用在爬虫这种大多数时间cpu都在等待而非计算的场景。

这里贴一份用多线程处理数据的代码。

start=time()
        feature=[]
        with open(self.file_name) as f:
            # lst=[i for i in f.readlines()]
            with ThreadPoolExecutor(16) as exec:
                result = exec.map(self.sub_df, f.readlines()[:100])
            for res in result:
                feature.append(res)
        print(time()-start)
        return feature

上面的代码是报错的,因为所有的线程都用到了同一个 tokenizer模型,所以报了错为:

RuntimeError: Already borrowed

但是如果这些线程调用的函数是纯代码是没有问题的。

最后就是多进程了,一个python解释器本质上就是一个进程,所以无论怎么折腾多线程,python还是单进程。那么多进程就是通过开多个python解释器来实现真正意义上的多进程。当然,这会增加python解释器创建方面的开销,但是面对海量数据的处理工作,这是一笔划算的交易。同时每个python解释器都会拥有自己的tokenizer模型镜像,我们也就不会担心报错了。

上面我们用单进程处理100个数据用时50s,这里我们用多线程处理1000个数据耗时130s!

贴代码和利用率图:

start=time()
        features=[]
        with open(self.file_name) as f:
            lst=[i for i in f.readlines()][:1000]
            with ProcessPoolExecutor(16) as exec:
                result = exec.map(self.sub_df, lst)
        for res in result:
            features.append(res)
        print(time()-start)

python PooledDB 多线程 python for 多线程_开发语言_02

顺便提一句,多进程比较吃内存,推荐直接把虚拟内存拉满。