Python 的多层嵌套循环如何优化?

2022-10-31 11:15:57 +08:00
 mmm159357456
result = list()

for x1 in list_a:
    for x2 in list_b:
        for x3 in list_c:
            // 任意层,xn 皆为 f 的必要参数
            _r = f(x1, x2, x3, *args)
            result.append(_r)

众所周知的 python 循环执行慢,如上情形如何优化?

6073 次点击
所在节点    Python
72 条回复
wxf666
2022-10-31 14:54:08 +08:00
@mmm159357456 为啥每一天( list_a )都要计算每一年( list_b )的指标。。

当 x1 = 2022-10-31 时,也要计算 x2 = 1970/1971/1972/.../2099 的指标吗???

还是说,你遍历 list_b 是为了找到 x2 = 2022 ?

list_a 为啥要按日排列?[2099-12-31, 2000-01-01, 1970-12-31, 2099-01-01] 这样的排列有啥问题吗?反正每个日子都要计算 range(1970, 2100) 年的指标的。。



可能你放出函数 f 的伪代码,大家能更好讨论
zzl22100048
2022-10-31 15:07:46 +08:00
你这个不是嵌套循环的问题
1. 先考虑是否需要全部遍历
2. a b c 三层是随机变化的还是线性增长的
3. 一次性任务直接 ray 跑就别优化了
mmm159357456
2022-10-31 15:21:46 +08:00
mmm159357456
2022-10-31 15:25:43 +08:00
@zzl22100048 我再看看吧,我现在想做优化的原因在于循环内的处理逻辑一直在变动,每次变动后需要一天的时间才能算出来一个模式(还不知道对不对),我着急...
另外:
1.我确实需要全部遍历
2.每一层的迭代对象我都预处理成固定长度的对象了
FYFX
2022-10-31 15:36:17 +08:00
你试着把 for 循环的数据构造一个 dataframe 然后和 data join(pandas 里面应该是 merge)然后再算结果呢
liuxingdeyu
2022-10-31 15:40:40 +08:00
@mmm159357456 我觉得三件事可能是有回报的。第一查又没有冗余的计算之类的,可以做个用点空间换时间或者 dp 优化一下。第二就是线程换协程,本质上就是省下来了线程切换的时间;或者直接一个进程里就单线程跑,多搞几个进程,Linux 下进程线程基本上一回事,切来切去的不如多进程搞完再汇总。第三就是把操作用 numpy 之类的 c++库搞一下。
TimePPT
2022-10-31 15:44:36 +08:00
看代码,感觉是不是可以用原始数据 pandas ,对 df 做 groupby 后再 sum 解决?
wxf666
2022-10-31 15:49:53 +08:00
@mmm159357456 我没看出 level 作用是啥。。


考虑使用数据库吗?感觉可以转成 SQL:

SELECT year(dateday), geometry, SUM(IF(el1 >= theshold, el1, NULL)), SUM(IF(el2 >= theshold, el2, NULL)), ...
  FROM ...
GROUP BY year(dateday), geometry


基本上,扫一遍文件就算出来了
mmm159357456
2022-10-31 15:51:31 +08:00
@FYFX #25 能具体说说吗?我这每个循环都相当于处理逻辑的实参

@liuxingdeyu #26 dask 已经这么做了

@TimePPT #27 可以 groupby ,这样的话就要涉及 multiindex 。另外我的机器内存放不下所有数据
mmm159357456
2022-10-31 15:53:40 +08:00
@wxf666 level 用于构建 dataframe ,我再考虑考虑
wxf666
2022-10-31 16:01:20 +08:00
@mmm159357456 我觉得根据 year(dateday), geometry 来 groupby ,要不了多少内存吧?

大概只需要:200 年 * len(geometry_list) 行,len(elements_in_data) 列
FYFX
2022-10-31 16:13:02 +08:00
@mmm159357456
就是用 for 循环中的那些数据,生成一个全量的表表头是 geometry start_date end_date theshold(不过我在你代码里没看到这个变量从哪来的),然后和 data 做内连接(而且像前面说的,data 数据可以先做一次预聚合),内连接就是你写的那些条件,然后结果应该就是你要得数据了。还有内存放不下的话,看着行数据之间是没有依赖的,我感觉可以拆 data 的数据分多次处理吧,然后再合并,类似于 map-reduce
mmm159357456
2022-10-31 16:16:01 +08:00
@wxf666
@FYFX
我去试试各位的方法,感谢
wxf666
2022-10-31 16:23:07 +08:00
@FYFX @mmm159357456 我觉得没必要做啥 join ,直接在 data 上 groupby 后,对 el1, el2, ..., elN 做 sum 即可(只累加 >= theshold 的值)

换成 SQL 应该是 28 楼那样

结果应该是 31 楼那样,(年份数 * len(geometry_list)) 行 x (len(elements_in_data)) 列 的表
specter119
2022-10-31 16:57:31 +08:00
是在一个很大的时间尺度上做滑窗吗?每个滑窗还要跨文件 IO ,机器还没法一下全读了?
个人经验即使是分布式的 spark 上,优化的空间并不大。而且 spark 上滑窗的计算也很慢,不知道新一点的 dask ,ray 这方便会比 spark 强多少。
mmm159357456
2022-10-31 17:00:00 +08:00
@specter119 对,我是在做 rolling ,感觉 dask 也没快到哪里去
Aloento
2022-10-31 17:01:13 +08:00
或许上 Cython 会有奇效
fairless
2022-10-31 17:02:31 +08:00
类似的场景,把那部分逻辑用 c 写了个模块,效率提升上百倍起步
nuk
2022-10-31 17:03:37 +08:00
把在硬盘上连续的放在最里面的 for ,这样 io 会快很多,其他的想不到了
mmm159357456
2022-10-31 17:07:08 +08:00
@fairless #38 哈哈哈,难度一下子上来了

@nuk 我试试

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/891370

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX