大佬求助,生成 6000 条数据居然花费 30 分钟?

2020-12-17 16:41:26 +08:00
 xzour

不知不觉,做开发已有 2 年,传统行业的 IT 开发,需求的大概是全能手,SAP 上了一期,对接了 SAP,了解了一些 ABAP 语法,写了几个接口,慢慢的对语言不在执着,对技术也不再执着,因为出身工厂,对工厂业务还算熟悉,对解决方案的思考还算比较热衷。所以就沉浸在计算的一块,但是技术算法认知的先天不足,所以想请大佬把把脉,以后的学习方向。比如以下一题:

  1. 一个财务的一个清账模拟功能
  2. 已知系统目前有几千个客户
  3. 计算周期为一月一次清账
  1. 有 3 个数组,第一个数组是客户组,大概有几千条。第二个数组是各个收款明细,有几千条,不一定全部客户都有,第三个数组是应收发票,大概有几千条,不一定全部客户都有
  2. 需求是每个客户的收款与发票对碰核销,记录每笔核销的记录及每个客户的期末余额。

目前这个模块我是通过暴力遍历实现的,但是速度太慢了,逻辑如下

  1. 遍历每个客户
  2. 读取该客户的收款及发票
  3. 遍历收款,取发票一条一条核销,一条销完,换另一张发票,未销完,记录发票 INDEX 及剩余金额
  4. 最后将结果批量插入数据库。 大概 6000 多条核销明细花了我 30 分钟+ 不可忍受。

因为没到月末,基本收款及发票是不一定真实的,有可能会冲销掉。实时清账就有可能反清账,所以目前清账功能财务需求是预清账,点击就跑,期末定了再关账。

第二个问题是关于动态计算的表达式引擎问题,2W 条数据动态计算,用了 C#的库,居然还要几分钟。 大概需求如下
一个单据,有很多属性,然后不同的属性,会对应不同的成本及利润计算公式。所以我把它放在前端用动态表达式的方式配置,而不是代码写 IF (因为情况大概目前有 70+种,再加以后的新的营销活动啥的,财务提一个需求就要改一次代码太麻烦,所以找了一些动态表达式的库,通过前端配置触发条件) for example.

  1. 某个人(参与 A 活动),提成是,毛利提成》销售额提成,则毛利提成,反之销售额提成
  2. 某个人是负责 A 区域的,结果此张单成交区域在区域外,提成需打折。
  3. 某个活动提成不计成本,直接按某个比例算提成。等等。

1.目前逻辑是,遍历 2W 条,先匹配布尔条件生效判定(可能不止 1 个布尔条件),但终归 70+种情况组组合,最后得到的算法,然后计算提成。

因为用了 C#的表达式引擎,Flee ( Fast Lightweight Expression Evaluator ),目前用时 3~5 分钟,但是这 5 分钟不太适合体感使用,因为在结算的时候,表达式会调整的,然后重算验证就等的不耐烦了。

题外话:公司的技术大佬是中小方案很熟练,BS/CS 端直接上手,打印报表配置,运维都可以,中小企业不可少的人才,但是技术深度不行,喜欢代码一把梭,项目 MVC 还是我来了之后分的,以前直接控制器+静态类打天下,我想了一下我又不想成为绑定公司的人才,所以对多语言的学习比较抗拒,对技术逻辑比较感兴趣,对技术遇到的难题,也喜欢思考,但是非科班,所以很多时候都是暴力算法,然后因公司上市,业务发展的复杂度也越来越高,传统行业对 IT 技术人才是不太尊重,感觉目前的代码不重构,会因为复杂度,迟早黑盒子一样的存在.....

3221 次点击
所在节点    程序员
28 条回复
Easzz
2020-12-17 16:51:26 +08:00
可以每天 /月生成历史数据,出报表直接统计天 /月的数据即可,不用查询明细。
linksNoFound
2020-12-17 16:57:38 +08:00
你看看 io 和 cpu 哪个跑满了,都还空着就试试多进程吧,比改代码判断逻辑简单
MakeItGreat
2020-12-17 17:00:20 +08:00
我好像觉得 Excel 更简单
xzour
2020-12-17 17:13:11 +08:00
@MakeItGreat 就是因为财务随着业务复杂度越来越高,汇总数据太麻烦,开始考虑上系统了呀。
whosesmile
2020-12-17 17:39:00 +08:00
我做前端的,看了第一个题目,瞎说下:
遍历每个客户感觉没必要,因为每个你无非是要找当前客户的的收款明细,倒不如直接一次性检索出所有的收款明细按用户 ID 分组,同样检索所有发票也按用户 ID 分组,然后这两个组都按用户 ID 排序,然后剩下的是算法层次的事情,找两个集合的对对碰。

前提是你的数据量不大,你的内存足够装下这么多数据,这里的优化是不需要每个 for 迭代都去查询 DB,全部换成内存操作了。
Bazingal
2020-12-17 17:39:59 +08:00
数组转字典,另外是不是可以遍历发票找对应的收款和客户
whileFalse
2020-12-17 17:40:01 +08:00
我估计你套了双层 /三层循环。请善用字典。
whosesmile
2020-12-17 17:41:15 +08:00
因为我理解你是每个月对账,所以这个用户的数据不是全库的,而是按时间维度收窄的,你的内存应该装得下的。
Bazingal
2020-12-17 17:43:55 +08:00
计算部分根据实际情况使用异步或者并行
whileFalse
2020-12-17 17:44:38 +08:00
另外,对“技术深度不行表现在没有 MVC”不能苟同。
xzour
2020-12-17 18:22:34 +08:00
@whileFalse 一个微型的 ERP 没有 MVC 分层思想不严重吗?
xzour
2020-12-17 18:24:44 +08:00
@whileFalse 具体表现为控制器方法都是为 3~1000 行的代码,db,业务逻辑耦合一起。
xzour
2020-12-17 18:31:11 +08:00
@whileFalse 大佬举个数组转字典的优化例子,不太懂怎么用。
xzour
2020-12-17 18:31:28 +08:00
@Bazingal 大佬举个数组转字典的优化例子,不太懂怎么用。
xzour
2020-12-17 18:33:26 +08:00
@whosesmile 目前是一次取 DB,客户,收款,发票,然后是内存检索客户对应的收款发票对冲的。 你的思路是个好思路客户 ID 是可以排序的。谢谢
laminux29
2020-12-18 01:16:48 +08:00
1.C/Cpp 以及传统主流数据库,对于 1000 * ( 1000 + 1000 )规模的两层 for 循环或游标遍历,甚至 1000 * 1000 * 1000 规模的 3 层循环或游标遍历,根本毫无压力。

如果有瓶颈,需要分析瓶颈在哪。

比如常见的错误实践,在编程语言中这样写:

for
----for
----.----for
----.----.----string SQL = "SELECT *....";
----.----.----db.search(SQL);

那么主要的瓶颈就在 [db.search(SQL);] 这条语句这里,具体一点就是编程语言的数据库客户端驱动,与数据库的接口调用这里。这种问题需要把程序逻辑全部移动到数据库的存储过程里,然后编程语言这边对数据库进行一次调用,瓶颈就解决了。


2.就算不改这些东西,总体时间也只是 30 分钟的话,可以考虑增加 N 台服务器,把数据分为 N 组,每台跑一组,这样总体时间可以缩减到 30 分钟 ÷ N + 汇总时间。
xzour
2020-12-18 09:09:23 +08:00
@laminux29 为什么要用存储过程这种方式?而不是在遍历之前把所有数据先查询好?是考虑内存限制的问题吗?在多数据源的情况下是不是不好处理了
l00t
2020-12-18 09:22:11 +08:00
@xzour #17 他是怀疑你每次都只读一条,这样大量时间耗费在数据库交互上。如果你一次全部读出来,那自然是无所谓,这个位置也不会是 db.search
no1xsyzy
2020-12-18 09:52:39 +08:00
@laminux29 6000*6000*6000 按 NOIP 估法,遍历总量除以 1e8 为秒数,大约 36 分钟
O(n^3) 增长挺快的。
no1xsyzy
2020-12-18 10:02:04 +08:00
第二个问题可以用局部表达式结果缓存来优化
动态修改的体感上的话,也可以通过每个被计算的对象依次计算,有值就推给前端来实现至少前几个能够立即响应(简单的 UX 设计)
甚至前端做动态滚动显示,滚动到的范围才会被求值(需要前端能力)

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

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

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

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

© 2021 V2EX