Shell:异构目录的文件双向同步(三)

原创
2021/08/11 11:57
阅读数 44

性能的烦恼!未完待续:

  • 调试比较函数时,因 get_file_sum() 耗时过长,不得不临时注释掉,运行时再取消注释,难受之轮回

  • 虽然复用了第一个同步结果,但为了不有所遗漏,还是把所有B盘和A盘进行了比较同步,此时检校的耗时无法再忍,如何提高比较的性能?

https://my.oschina.net/u/1044667/blog/5176228

小象过河

小象丹波之前在查看任务管理器时就发现,只有一个进程在做 hash,且大部分时间都花在文件读取上。但 CPU 是四核的,hash 操作之间也没有依赖,完全可以并发。

并发 hash

不禁想起了 MapReduce,其实就是做好这件事:批量处理文件,留下独一无二的。

还是要先把文件集中在一起,再批量并发处理,最后筛选。

修改比较函数,不再直接获取文件hash,而是做好文件标记

if [[ $(get_file_sum "$fpath") -eq $(get_file_sum "$path") ]]; then
unset fileStat[$fpath] fileStat[$​​path]
fi

fileStat[$fpath]+="U"
fileStat[$path]+="U"

U 就是没定(Un-decided)的意思。

# 批量并行求取 hash(Map),并去除相同 hash 的文件(Reduce)
declare -A fileSum
for path in "${!fileStat[@]}"; do
    [[ ${fileStat[$path]} =~ U ]] && echo "$path"
done | xargs -i -P 4 sum -s "{}" | while read -r line; do
    sum=$(cut -d' ' -f1 <<< "$line")
    path=$(cut -d' ' -f3- <<< "$line")
    if [[ ${fileSum[$sum]+_} ]]; then
        fpath=${fileSum[$sum]}
        unset "fileStat[$fpath]" "fileStat[$path]"
    else
        fileSum+=([$sum]="$path")
    fi
done

说明:

  • xargs -i -P 4-i 使用默认占位符{};-P并行进程数

这样一来,计算速度就提升了 4倍 啊~

文件名模糊比较(Fuzzy)

经过这些时间的折腾,对情况也算比较了解了,发现很多相同文件尽管文件名不同,但差异也不算大,能否用模糊比较,只要差异不太大,就认为是相同文件呢?

搜了一下,能做字符串模糊比较的库倒是有,但能直接用的命令没找到,倒是发现 python 包不少。无所谓,用吧,至少比求文件 hash 快。

# 字符串编辑距离
similarity() {
    local str1=$1
    local str2=$2
    python -c "import Levenshtein; print(Levenshtein.distance('$str1', '$str2'))"
}
# 比较函数文件名部分
# 相似文件名(编辑距离5以内)
if [[ $(similarity "$fname" "$name") -le 5 ]]; then
        fileStat[$fpath]+="S"  # 相似标记
        fileStat[$path]+="S"
        continue
fi

有了标记,最终生成同步结果时,可以抛弃,也可以进一步处理。

hash 缓存

既然大部分文件是相同的,那就意味着在各个U盘的对比中,同一个文件的hash会不断重复,显然值得进入缓存,重复使用。

请珍惜每一个 hash,那就是你宝贵的时间财富!

# 在并行hash后,使用tee直接保存hash结果到m.txt
xargs -i -P 4 sum -s "{}" | tee -a m.txt | while read -r line
# hash 缓存初始化
declare -A sumCache
init() {
    sort -bu sum_cache.txt m.txt -o sum_cache.txt  # 持续合并hash结果
    local sum path
    while read -r line; do
        path=$(cut -d' ' -f3- <<< "$line")
        sumCache+=([$path]="$line")
    done < sum_cache.txt
}
# 使用缓存的并发 hash
: > m.txt
for path in "${!fileStat[@]}"; do
    if [[ ${fileStat[$path]} =~ U ]]; then
        if [[ ${sumCache[$path]+_} ]]; then
            echo "${sumCache[$path]}" >> m.txt
            continue
        fi
        echo "$path"
    fi
done | xargs -i -P 4 sum -s "{}" | tee -a m.txt

优化效果

  • 对于反复调试来说,不用再注释 hash 部分代码,缓存很快
  • 对于后继的 U 盘文件对比,时间一般保持在 5~10 分钟内。由于缓存的合并,越往后时间越短

经验总结

  • 对于耗时较长的操作,如果因错误而中断,或需要反复调试,会非常不方便。尽量把中间数据另存为文件,方便恢复现场或定位bug。进一步,考虑让中间数据作为缓存对象,提高效率。
  • shell以它的直接(丰富的系统自带命令)、纯粹(命令只做好一件事,极具工匠精神)、直观(字符串交换,对人友好),直入人心!但整体设计很像程咬金的三板斧,如果不能很快摆平问题,对于越来越复杂的代码,各方面的不足会逐渐暴露出来。这时可以考虑及时切换到 python,毕竟对数据结构的支持更好,且可用的库也非常多,基本开箱即用,比肩 shell 命令的丰富灵活。
展开阅读全文
加载中

作者的其它热门文章

打赏
0
0 收藏
分享
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部