multithreading discordbackups.20.03.30

没想到在缝合了包括discord drive脚本等一大堆脚本之后,我真的把多线程重新上传附件给做出来了,而且效果相对原版可以说是立竿见影,只需10个线程,拖ucc的冻鳗频道只花了28分钟42秒,拖ucc的mlp频道只花了两分钟48秒,现在还在测试nsfw频道,估计不会超过两小时🤔

总体来说对discord附件的处理,有两种方式,一种是在遇见附件的时候处理它并即时替换消息然后再写入dump文件;另外一种方式是先dump一遍消息,在此过程将需要重新上传的文件全部列出来(当然生成aria2链接的时候理论上来说就包含了所有信息,但那不够,我需要再生成一个metadata文件),然后在另外一个函数中将这些文件全部处理一遍,获取原链接和重新上传后新链接的对应关系,然后用sed命令实现替换,由于discord链接只有一个斜杠(可能还有双引号)是sed的元字符,把它转义掉即可

当然如果是py等高级脚本语言的话,理论上可以在dump消息的时候就将附件推到每个重新上传线程的待处理队列里面,这两步都可以并行,但bash并不是这种高级脚本语言,所以只能分两步走了

和discord drive相比,我还需要实现将大于8MB的文件交给nitro线程上传这个功能,考虑到实际情况,我强制规定数组0为nitro线程(即只有一个nitro线程),别的线程从1开始;接下来分配线程的时候只需要一个for循环就可以像以前一样干了

由于metadata文件里包括了附件的文件大小,遇到大于8MB的文件直接推给nitro线程,剩下的写个求余按顺序推给剩下的线程

另外我发现将for列举线程内文件的循环写在线程函数之外和之内都能正常工作,所以我还是写在线程函数之内比较好看;看来唯一需要注意的点几乎只剩下了将里面所有变量前面都加上local🤔

这么搞的话尽管从discord上下东西的速度是快了不少,但之后sed时就蛋疼了,毕竟现在它需要在一大坨消息dump里找到并替换附件url,时间复杂度瞬间从O(n+2m)变成了至少O(2nm),其中n是消息条数,m是附件数量,乘2是因为需要替换的地方有两处

比如搞ucc的nsfw频道时前两个步骤只花了可能一个小时,但sed替换时一秒钟才能替换1到1.5次,而需要替换的消息有一万多条🤔

这还真TM操蛋,不过除了移到本地linux外还有什么加速的方法?🤔可能需要重写一遍scheduler算法,直接将dump文件拆成线程个数份,然后除了需要扔给nitro线程的文件外,其他线程生成的新链接只需要替换它对应的dump片段,最后再将替换完成后的片段合并,这样时间复杂度就变成了O(t(n/t)(2m/t)),取决于sed可不可以多线程,前面的那个t也许可以去掉🤔

UPDATE:草,终于写完了多线程,无论(重新)分析、上传和sed替换都做了多线程,发现尽管sed时间缩短了十万甚至九万倍,但别的开销大了起来:

  • 由于不知道消息的具体数量,没法在一开始dump的时候分析,只能等dump完后分析,由于要保证前向兼容(获取原始aria2列表),需要重新分析(但这个也可以写成在分析阶段做,倒是没什么)

  • 最主要的是附件的分布并非均匀,所以有的dump片段就会比别的片段多些附件,甚至多出几十到几百件,这样别的线程需要等待这个线程

实测优化后的方法拖ucc的mlp频道居然比优化前多整整一分钟,就是在等那个多出来的线程;冻鳗频道花了26分钟32秒,快了两分钟多一点;我估计nsfw频道会是这种优化方法的最大受益者,毕竟之前光sed替换就花了67分钟49秒,而在优化后相同的过程(我光注释了上传的代码,然后生成了假的sed替换文件,如果不考虑附件不均匀导致的开销,可以认为是优化后的表现)只花了15分钟46秒🤔

由于我不小心ctrl+c掉了nsfw频道的dump过程,它sed到一半好像花了86分钟,我估计和discord交互的时间是52分钟左右🤔

对了,在比如死🐴管理一个小时后就要删聊天室的极端情形下,优化后的方法由于有重新分析和线程间不均匀等额外开销,搞不好会导致来不及备份完很多附件就消失了的额外风险,而之前的方法没有这个风险,而且由于给线程分配任务是按照余数的方式分配,它会尽可能均匀,最多只多出一个任务🤔

我的测试表明基本上在一万条消息以上,优化后的方法在时间上才有优势,而且会因为时间复杂度的二次方性优势更大

比如拖十万条消息和附件时,原来的方法可能要花以天计的时间来完成sed,当然此时已经和discord上面被删了的频道没有半毛钱关系了🤔

future

接下来我打算实现个迫真profiler,来标记出每个阶段需要花的时间,反正time命令的结果貌似只能输出到stdout或者stderr,如果设置重定向的话恐怕所有东西都从屏幕上消失了;我要么查下吧

然后就是想办法实现备份整个guild的脚本了,只需要列出所有channels,然后设置好每个channel对应的参数,然后就可以调用这个脚本了;主要是需要备份整个guild的时候情形都非常极端(比如旧/r/megatan即将被删前几个小时),时间真的很珍贵,估计所有频道都得设置成仅文字或者旧多线程方法

另外我需要想办法测试下一个频道被删之后里面的附件能保存多少时间,我看哪天想办法设计个实验,但现在还是算了吧

附录

用优化前方法拖mlp频道时间:

real 2m48.221s
user 1m38.277s
sys 1m26.461s

用优化后方法拖mlp频道时间:

real 3m19.677s
user 1m35.828s
sys 1m31.185s

用优化前方法拖冻鳗频道时间:

real 28m41.908s
user 16m8.481s
sys 14m10.055s

用优化后方法拖冻鳗频道时间:

real 26m32.508s
user 13m26.061s
sys 13m55.378s

优化前nsfw频道执行sed替换所用时间:

real 67m49.196s
user 33m38.716s
sys 31m18.762s

优化后nsfw频道重新分析、执行sed替换等所用时间:

real 15m45.703s
user 10m1.709s
sys 17m54.682s

优化后拖nsfw频道所用时间:

real 67m12.181s
user 35m12.408s
sys 40m22.244s

至于优化前的时间,反正我懒得测,当然也不一定(bruh

UPDATE.20.04.12:在vultr 512MB内存vps上拖ucc的迫真赛博监狱频道所用时间(由于司马cord不支持ipv6,我只能多花点钱上个ipv4实例):

real    12m16.441s
user    3m50.691s
sys     3m23.343s

发表评论