11
8
2018
4

与 Android 进行 WLAN Direct 连接

首先 iw list 看是否支持。如果支持,那就

iw dev wlan0 interface add p2p0 type __p2pdev

这样其实并不会多出一个叫 p2p0 的网络接口。iw dev 能看到多了个「Unnamed/non-netdev」设备。不执行这个也可以连接上 WLAN Direct,但是当前的 managed Wi-Fi 连接会断掉。执行之后再连接,managed 连接会持续,iw dev 里会有两个 Unnamed,不知道何故。另外这个 type __p2pdev 加上去了我就不知道怎么删除了。试了几个命令,结果搞得内核 oops 了……

然后是 wpa_supplicant 配置文件:

ctrl_interface=/run/wpa_supplicant_p2p
ap_scan=1

device_name=起一个名字
device_type=1-0050F204-1

driver_param=use_p2p_group_interface=1

wpa_supplicant 跑起来。注意这里的接口名还是那个 managed 接口的。

wpa_supplicant -i wlan0 -c p2p_config.conf

然后 wpa_cli 连过去操作:

wpa_cli -p /run/wpa_supplicant_p2p

首先用 p2p_find 开启搜索。这时候对端设备能够看到自己了。使用 p2p_connect 对端MAC pdc go_intent=0 连接,在对端接受连接即可。go_intent=0 是让对方作为 group owner,这样对端 Android 才会提供 DHCP 服务(否则要本地提供了)。

然后就可以给自己添加 IP 地址了。此时是可以用 dhcpcd 的,然而直接跑的话它会抢走默认路由,所以知道地址范围之后手动加一个好了:

ip a add 192.168.49.22/24 dev p2p-wlan0-1

Android 设备的地址是 192.168.49.1。

之后就可以用 adb connect 然后 scrcpy 了。

PS: Android 很喜欢四十几的 IP 段呢。USB 网络共享是 192.168.42.129/24,Wi-Fi 网络共享是 192.168.43.1/24,而 WLAN Direct 是 192.168.49.1/24。不知道蓝牙网络共享是多少呢。

PPS: scrcpy 在我的 XZ2C 上运行完美,但是在 MIUI 10 上是「只读模式」,只能看,所有交互操作无效。

参考资料

Category: 网络 | Tags: linux 网络 Android
10
11
2018
19

获得高精度环形镜子一枚

如图:

高精度环形镜子

这面镜子上边有好几百G数据呢,然而早已读不出来了。

镜子来源于九年前的西数移动硬盘。因为太老了所以坏掉了吧,反正是识别不了了。最近看到 YouTube 上有人各种折腾硬盘,群里又有人拆了好几面镜子出来,所以我把它找出来拆啦。这镜子比我那总也擦不干净的丽塔芙镜子清晰多了呢,就是小了点儿。

拆起来也不难。首先想办法把壳弄开。然后见着螺丝就旋下来,见着贴纸就撕掉。那覆盖一个螺丝孔的小圆片不用撕下来,直接螺丝刀往中间捅,然后把螺丝旋下来就好了。

HDD 拆解中

覆盖磁头的那块金属有两个螺丝,其中一个在洞里。它上边有块形状奇怪的磁铁,照片里吸了很多螺丝的就是了。磁头是直接翻个面,让重力把它拉下来就可以了,我之前以为还有螺丝之类的还弄了好久呢,结果手一翻它自己掉下来了。马达中间那个螺丝很不容易取下,因为整体会跟着转。我是用剪刀卡住它的两个洞才给旋下来的。剩下的手拆就可以了。

HDD 拆解完毕

Category: 硬件 | Tags: 硬件 硬盘 拆解
9
28
2018
14

每次修 Python 代码的 bug 的时候总会想念 Rust

俗话说:由俭入奢易,由奢入俭难。

之前写 Python,老是在实现完一个特性之后,弄出来几个 AttributeError: 'NoneType' object has no attribute 或者 TypeError: list indices must be integers or slices, not str,还有 TypeError: can only concatenate str (not "int") to str 这样的错误。一看就明白自己又是哪里一不小心疏忽了,稍微修一下就好。

后来啊,我遇见了 Rust,整个流程就变了。之前写的时候,基本上都是通过手动测试来发现这种问题。为了高效、不破坏性地测试,需要控制测试的数据量,需要保证出错的时候相关的数据不会处于某种中间状态。当然在服务器上跑的脚本,我还要来来回回地传更新的脚本,或者弄个本地测试环境。而这一切,可能不过是为了跑一个成功之后再也不会用到的小程序,比如之前分析抓包数据的那次。而在 Rust 里,这些最容易犯的错误,cargo check 一下,编译器基本上能全给你指出来。所以有时候写一些小工具我也用 Rust,虽然写起来慢,但写好就能正常运行,不用反复试错,多好啊!

最近给 Arch Linux 中文社区的自动打包机器人 lilac 增加新特性。结果实现完部署之后,夜里就被 lilac 叫起来修 bug 了,还一下子就是仨……(lilac 很难本地测试,而短暂地服务中断又没多大影响,所以我都是不进行本地测试的。)

第一个 bug 是,与 dict.get 不一样,getattr 是没有默认值的。Python 里这种不一致很多,比如 configparser 里默认值要用关键字参数指定。Rust 遇到类似的情况,就会返回一个 Option。或者如果 API 决定如果不存在就 panic 的话,那么它就会直接返回我要取的值的类型,而不会包一层 Option。而我后边的代码是预期到这里可能取不到那个属性的,所以弄错了就会类型不匹配。

第二个 bug 是局部变量在一个分支上没有初始化。Rust 当然不会允许这种情况了。实际上 C 都不用担心这种问题,编译器会给出警告的,还有一些 linter 可以用。而 Python,很遗憾的是,我所使用的 pyflakes 并没有对此发出警告。我当然知道 pylint 那些。我很讨厌 pylint 和 jslint 这种不区分潜在 bug 和风格问题的 linter。我只需要工具在我可能疏忽的时候提醒我,而不需要它对我的编码风格指指点点,特别是那些指指点点往往是不对的。比如我的文件描述符变量名不叫 fd 难道要叫 fildes?

第三个 bug 是一个可能为 None 的变量我忘了先作 is not None 判断。这段代码如果初写的话我肯定是会注意到的,但是改的时候,只想着如果 pkg 里有冒号我得处理一下,就忘记了根本没有关联的包名的情况。Python 的 None,以及 C 和 C++ 的 NULL、Java 的 null、Lua 和 Ruby 的 nil、JavaScript 的 undefined 和 null,被称作是十亿美元错误,给无数程序员和用户带来了无尽的 bug。幸好这个东西在 Rust 里不存在:表达「没有值」的值没有被作为特殊值存在于几乎所有类型中,而是作为一类类型的可能的值之一。想要使用「正常」的值,就需要显式地进行类型转换,所以不可能被不小心忽略掉。顺便说一下,Go 里也有 nil 这种东西,以至于会出现这种不容易发现的 bug

Python 现在也给出了解决方案:类型注解,提供类似的类型检查。不过检查器是第三方的,也并不十分完善。等我找到机会试用过之后再来写感想啦。

Category: python | Tags: python 编程 编程语言 Rust
9
20
2018
8

永远不要 tail -f 管道

运维同事为了收集日志,配置程序将日志写入一个命名管道。然后他在外边拿 tail -f 去读,结果发生了灵异事件。通过 strace 可以看到,tail 进程读取了日志,但是却并没有再输出来。但是如果不启动输出日志的程序,而是在实例启动之后再进去往管道里写数据,却又是可以立即得到 tail 的输出的。

很奇怪的行为,一群人在那里研究半天,猜测是不是环境变量造成的啊,是不是放后台组执行造成的啊,是不是 XYZ 啊。——典型的「霰弹枪式」除错法

我当时也被带沟里了。于是跑去尝试复现,接着去读 tail 的源码。花了好久才明白这是一个很简单的问题:tail -f 的语义首先是 tail 这个词本身——先读文件最末尾的数据(默认是十行),然后再是 -f 选项的语义,即在文件更新时接着读取数据并输出。所以,当程序往里边写日志时,管道写端一直没关闭,tail 就一直读不到文件结束,也就无法确定最后十行是什么。当他们测试的时候,因为使用的是 echo shell 命令,打开文件、写入数据、关闭。这样 tail 一下子就读取到了文件末尾,然后把数据输出来了。接下来就是边读边输出了。

其实这种使用方法本身就很奇怪了,以至于这个执行流是兼容许多系统的 tail 的各种分支里,最最不常规、无可奈何的那一个分支。你都用管道了,cat 一下嘛。如果怕遇到管道被 reopen 的情况,就在 while true 里 cat 就好。

这个事件中,我也是见识了很多人解决问题的奇怪思路:「我猜猜猜。猜对了哦耶,猜错了,哎呀编程好难啊,Linux 系统好难啊……」猜你妹啊!你长的是大脑又不是骰子,用逻辑一步一步地取得结果不好吗!

有一个小游戏——猜数字。比如甲确定一个 1 到 1000 之内的整数,然后乙来猜。每当乙给出一个猜测时,甲回应猜对了,还是过大或者过小。如果乙知道什么叫二进制的话,乙可以保证在十次之内猜中的。

计算机系统和编程世界里,最棒的一点是确定性和逻辑性。虽然经常也不是像上例那样完全确定的,但至少比起人类社会要容易确定得多。特别是在有源码的时候。所以解决问题的路线也很简单,顺着问题的症状一路回溯,确认然后排除那些没有问题的部分,逐步缩小问题所在的范围,直接你看见它。就跟上边的猜数字游戏或者地毯式搜索一样。每一次猜测都是带着排除一部分没有问题的地方而去,而不是明明有证据表明某个地方不可能有问题,你还偏偏怀疑问题在那里,做无用功。

就像调查一个凶案,这些人放着有作案嫌疑的人不管,非要费劲地去调查那些有相当好的不在场证明的人。

Arch Linux 中文社区这边也有很多这种人。出了问题描述不清楚症状。新手嘛,没经验也没学习过如何描述事实,讲不清楚也没什么,引导对方获取截屏、日志,逐步排查问题就好了嘛。可就是有些热心人,喜欢提出自己的猜测。重点是:都不尝试证实猜测是否属实,就急着上解决方案。结果就是,我询问细节事实的消息没人理,求助者试试这个,试试那个,最终问题能否解决,就跟买彩票能否中奖一样,全凭运气。

Category: Linux | Tags: Arch Linux fifo linux 社群
9
16
2018
10

人生苦短,我用 skim

前两天我又看到了基于子序列匹配的字符串过滤工具 fzf 的绚丽效果了。实际上我很早就听说了这个工具,只是懒得动手配置。此次提及,我发现 fzf 已经在官方软件源里了,而我也正好有时间,所以打算试一试。

然后呢,Arch Linux CN 群组里艾穎初提到 skim 这么一个工具。了解了一下,这个就是 Rust 版本的 fzf,并且在 archlinuxcn 源里也有(git 版本,即 skim-git)。这太好了,就是它了!

skim 的操作很简单。文章开头的链接里已经有效果演示了。常用的也就是输入子序列去过滤,然后再输入一个进一步过滤,直到看到想要的。使用 ! 前缀可以反向过滤,^ 匹配开头 $ 匹配结尾。Ctrl-p/n 来上下移动。提示符那里也支持通常的行编辑。

到现在为止,我自行实现了 sk-cd、sk-search-history、sk-vim-mru 三个功能。另外使用了自带的 completion.zsh 文件。由于各种不满意,没有使用自带的 key-bindings.zsh 文件(也就包含 cd 和历史命令搜索功能啦)。

completion.zsh 里目前有两个功能。kill 时通过 ps 补全进程 pid。这个想法很好,以后我可能专门做一个通用的方便 strace 啊 lsof 啊 gdb 啊之类的用。

另一个是遇到两个星号(**)时按 Tab 补全,查找并替换成当前目录下的文件。

我实现的 sk-cd 是从 autojump 取目录列表,然后喂给 skim。于是就成了交互式的 autojump~这是一个我很需要的功能。原来我都是通过 Tab 补全列出可能的项,然后再 Tab 过去选的,有些慢也有些麻烦。

sk-search-history 就是在历史命令里找东西。因为遇到特殊字符时无法正确地加载预览,我并没有开启预览功能。反正找到的命令只会放在命令行上,并不会自动执行的,选错了可以及时取消。

以上两个功能分别绑定到 Alt-s d 和 Alt-s r 上。我使用 Alt-s 作为 skim 快捷键的开头,以便保留 zsh 原本的快捷键,避免冲突,特别是以后可能会有更多功能被加入。我在 Vim 里,也是类似的做法,Alt-q 是 easymotion 的开头快捷键,Alt-d 是 denite 的开头快捷键。

sk-vim-mru 仅仅是个命令了。使用的数据是 mru.vim 的历史记录文件。然后做了两个函数:vim-mru 使用 Vim 编辑文件,vv-mru 使用我自己的 vv 命令在已有的 gVim 里编辑文件。

我做的版本和 skim 自带版本,最大的差别在于,我的版本会尽量使用全部的窗口空间,而 skim 自带的总是会使用 40% 窗口高度。(所以我有个函数用来获取当前光标位置,有需要的可以自己拿去用。)

如果你想用我的配置,可以 wget https://github.com/lilydjwg/dotzsh/raw/master/plugins/sk-tools.zsh 回去,然后 source 一下就好。有需要的话(比如数据来源、键绑定等)可以自行修改。


2018年09月17日更新:我尝试了一下把 sk-search-history 映射到 Ctrl-r 上,然后很快就放弃了。因为 skim 的结果是不可预测的,而默认的 Ctrl-r 的结果是完全可预测的(只要还记得;当然你不能开(那个让我在服务器上误杀过进程的)实时历史共享)。可预测性对提高效率非常关键,因为你不需要中断思维,停下来等结果。

Category: shell | Tags: linux shell zsh Rust
8
4
2018
21

XZ2C: 没有 root 的日子(也还过得去)

现在手机两三年不换新,日子就没法过了!所以我明知没有 root 还是买了 Sony Xperia XZ2 Compact(XZ2C),用于替换之前使用的 Z5C。

嗯,还是 Compact 版本。尤其是尝试在地铁上操作小米 Note 3,结果发现为了避免自己跌倒或者手机被摔地上,我不得不使用嘴唇来点击部分区域的时候,我决定绝不放弃能够安稳地握紧的 Compact 版本了。

啊,我知道有单手模式的!XZ2C 底部那三个虚拟键的位置,向左或右滑,就可以启动了,很方便!然而,最需要此功能的小米 Note 3,使用的是实体触摸按键,中间还是个凹下去的指纹识别,我很难成功启动单手模式。而且由于手机过大,这个动作操作的时候,手只能握住手机的下半部分,重心还是悬在外边,列车适时地颠一下说不定就会脱手而出了。

XZ2C 比 Z5C 重了很多,背面还是弧面的,手感意外地很棒呢。指纹识别在背部,拿在手里的时候倒是很好按,也不会有小米那握不稳的问题。不过你们懂的,放桌面上的时候,不拿起来就按不了。而且双击唤醒屏幕被禁用了(除非已抬起手机,屏幕已显示时间),密码解锁也并不方便。双击唤醒大概还是可以启用的吧,不过 Z5C 拿在手里走路的时候,屏幕经常被会意外点亮不说,还会一不小心就把锁屏时钟给换了样式。所以就不用啦。

这指纹解锁速度比起 Z5C 的侧面那个小的,快了非常多。而且可以配上很好看动画效果啊!就是 Sony 宣传片里的那种,不过它竟然不仅仅是在主屏幕上呈现,在任何应用打开的时候也能出现呢。

哦还有,XZ2C 充电、看视频时都不会像 Z5C 那样烫了~

系统是 Android 8 Oreo。最棒的莫过于通知管理了!之前我一直希望某些不重要的通知出现,但是不要发出声音(比如微信的「加好友」通知),但是只能寄希望于应用本身提供这种设置。现在不用啦,系统通知管理可以直接设置了。而且应用还可以给通知分门别类,然后用户按需要给不同类别的通知不同的设置(比如 Telegram 就能精确到会话)。在通知出现的时候,长按选择「所有类别」打开设置的时候,对应的类别也会闪动一下,不用用户去猜这是哪个类别的通知。

其次是应用图标的长按功能,可以查应用信息,可以访问应用提供的快捷入口,也可以把这些入口添加到主屏幕,或者添加该应用的小部件。而且应用信息里可以直接打开 Google Play 了!我之前都只好去搜索应用名的。在 YouTube 上看到有人直接把应用图标拖到一个控件上就能打开商店,然而我并不知道那是什么应用。

还有很重要的一点是:运行很流畅!不只是我以前新手机刚到手的那种流畅,而且装个应用一两秒、开机启动完了我还没反应过来,超出了预期好多!当然并不是说开机刷一下子就完了,而是我没想到 Android 的开机能有这么快。

啊,最后进入正题:没有 root,只能找替代了。

首先是权限管理。Android O 的权限管理已经强大了不少,而且我已经把大部分国内应用转移到小米上了,所以还好吧。

时间显示秒。这个功能,在「系统界面调谐器」里已经有选项了。长按下拉通知出现的齿轮图标,直接它转动起来,然后就可以在「设置」里看到「系统界面调谐器」了。

互联网访问。放弃修补好的 fqrouter,发现现在的主流软件其实也挺不错的。只是偶尔需要手动切换节点了。另外就是 SSRR 这个东西,连接上的时候如果切换节点,有大概率导致网络故障(DNS 解析异常等)。需要重启手机才可解决。

绿色守护。被黑阈取代了。但我发现其实需要黑阈掉的应用也不多。我目前添加到黑阈名单里的有微信、彩云天气、企业微信、Inoreader、形色、高德地图等,基本上都是国产应用。微信一大堆服务在后台跑着,虽然支持 FCM(GCM 升级版),被黑阈干掉之后会收不到新消息(而 Telegram 和 Twitter 什么的好像都能)。

Wi-Fi adb。这个其实开机后接 USB 线,adb tcpip 5555 一下,就可以一直通过网络连接了。黑阈等需要通过 adb 执行命令的应用,也可以自行调用。

「去你大爷的内置浏览器」。现在火狐支持 Custom Tabs 了呢,所以大部分应用都能够调用火狐了。虽然 Custom Tabs 里是不加载扩展的,不过再选择在火狐中打开就可以了,大部分内容都缓存了,再加载一遍也是挺快的。Inoreader 有「webview」、「chrome tab」和「外置浏览器」三种方案,其中「chrome tab」其实就是「Custome Tabs」。Google 自家的大部分应用也会使用「Custom Tabs」,除了「Google 搜索」仍然坚持调用 Chrome,不过它可以设置为在外置浏览器中打开。「新闻与天气」也需要设置为在外置浏览器中打开,不然因为没加载扩展,会有大量广告辣眼睛的。微信当然是无解的,只能在需要时浪费些流量和时间手动选择在浏览器里再开一次了。

五笔输入法。这个之所以和 root 权限有关,是因为之前我都是拿 XPrivacy 禁用输入法的联网能力,所以什么百度啊触宝啊,我都能接受。现在没法断它网了,我自然是不会再用不开源又非系统自带的输入法了,尤其是触宝这种,一联网立马弹出好几个广告的。一开始找到叫「五笔输入法」的试验品。功能很简洁,但不愧是试验品,不能输入中文标点也就算了,一次输入英文还不能超过四个字母……然后想起 trime——rime 在 Android 平台上的版本。使用感受是:我从未经历过如此简单的码表导入!把我的 fcitx 码表导出,然后一行命令调整一下格式就可以了!然后把码表和五笔输入方案推到内部存储设备上,「部署」一下,我终于可以在手机上使用我自己的码表了!

ssh、备份和调试。这个没办法。通过 termux 能很方便地启动个 sshd,但是权限很有限,比如只能只读访问外置 SD 卡。所以我通过 adb 起了一个 dropbear 来同步这些内容。系统分区没办法访问,只能依靠 Google 和 Sony 的在线备份功能了。

啊对了,Sony 的「Xperia 换机助手」真是垃圾啊。首先,很多应用不能带数据迁移。其次,我这里一次传输只能传一项内容!不然就会出现连接错误。好不容易在论坛上找到的方案,一次只传少量数据,所以短信和通讯录什么的都可以过来,小一些的应用也能过来。大的应用就没办法了。

还有些小功能。通话振动。这也是个 Xposed 模块,就是电话接通的时候振动一下。属于锦上添花的功能,没了就没了吧……还有 Xposed Torch 也是。不能长按音量键开手电筒,那就划开通知点按钮好了。微X模块,很好用,但也不是非要不可。

整理下来,因为没有 root 而失去的功能并不是很多,最严重的是没有完整、增量的备份了。其它的,相对于运行流畅所带来的体验提升,其实并没有那么重要。这次我真的是换了手机才知道旧手机已经全面卡顿了……

Category: Android | Tags: Android 手机 SONY
7
16
2018
1

使用 iptables 透明代理 TCP 与 UDP

很早之前,我在《Linux「真」全局 HTTP 代理方案》中介绍了 redsocks 方案。不过它只处理了 TCP,并没有处理 UDP,DNS 也是采用强制 TCP 的方式来处理的,再加上它本身还要将请求转发到真正的代理客户端,延迟比较高。然后,还可以结合 Wi-Fi 分享 或者网络命令空间,玩点更有趣的。

首先要有支持的代理客户端,比如 ss-redir。这个就不用多介绍了,配置好、跑起来即可。以下假设此代理跑在 127.0.0.1 的 $PPROT 端口上。

然后,TCP 的代理设置。使用的是和 redoscks 一样的方案。这个比较简单,除了有一点需要注意:DNAT 到 127.0.0.1 时,需要设置内核选项net.ipv4.conf.all.route_localnet=1

最麻烦的是 UDP 的代理,使用的是 TPROXY。首先,需要把要走代理的数据包路由到本地。以下假设我们给要代理的数据包打上标签 1。那么执行:

ip rule add fwmark 1 lookup 100
ip route add local 0.0.0.0/0 dev lo table 100

那个 100 是路由表的编号,可以自己选一个喜欢的。

然后,对于转发流量(来自局域网或者另外的网络命名空间),直接把需要代理的数据包扔给 TPROXY 目标,并且打上对应的标签即可。而对于本地产生的流量,不仅要带有对应的标签,而且需要在 OUTPUT 链打上一个(与之前不同的)标签,触发 reroute check 才行。

最后,对需要代理的数据包设置 iptables 规则:

协议 来源 目标
TCP 本地 nat OUTPUT -j REDIRECT --to-ports $PPROT
转发 PREROUTING -j DNAT --to-destination 127.0.0.1:$PPROT
UDP 本地 mangle OUTPUT
PREROUTING
-j MARK --set-mark 1
-j TPROXY --on-port $PPROT --on-ip 127.0.0.1
转发 PREROUTING -j TPROXY --on-port $PPROT --on-ip 127.0.0.1 --tproxy-mark 1/1

比如来自网络命名空间或者局域网的 IP 段 192.168.57.0/24 全部走代理:

iptables -t nat -A PREROUTING -p tcp -s 192.168.57.0/24 ! -d 192.168.57.0/24 -j DNAT --to-destination 127.0.0.1:$PPROT
iptables -t mangle -A PREROUTING -p udp -s 192.168.57.0/24 ! -d 192.168.57.0/24 -j TPROXY --on-port $PPROT --on-ip 127.0.0.1 --tproxy-mark 1/1
Category: 网络 | Tags: linux 网络 iptables UDP
7
11
2018
14

Linux 下获取文件的创建时间

其实 Linux 是支持文件的创建时间的呢。不过不是所有文件系统都支持,比如 ext4、xfs、btrfs 都支持,zfs、vfat、ntfs 不支持。

但是呢,用户基本上是看不到的。文件系统有记录,但是没有 API 可以获取到这个数据。所以你用 stat 命令的话,会看到「创建时间」一行总是「-」。用 debugfs 搞 ext4 是可以的,但是那个需要 root 权限,并且一不小心会搞坏文件系统。

最近,我阅读内核源码时,忽然发现内核已经通过 4.11 版本引入的 statx 系统调用支持获取创建时间了。字段名里用的是 btime(birth time),没有用 crtime(creation time),也没有用大写的 Btime 呢。

但是 glibc 并没有支持,所以要用 syscall 函数来调用。也不是很复杂。不过我正着手用 Rust 实现的时候,却在内核源码树里找到了 samples/statx/test-statx.c 这么个文件。原来有现成的啊!

gcc 编译一下,还真好用:

>>> statx /
statx(/) = 0
results=fff
  Size: 224             Blocks: 0          IO Block: 4096    directory
Device: fe:01           Inode: 96          Links: 17
Access: (0755/drwxr-xr-x)  Uid:     0   Gid:     0
Access: 2018-07-11 13:33:08.659477830+0800
Modify: 2018-03-30 15:06:02.645864827+0800
Change: 2018-03-30 15:06:02.645864827+0800
 Birth: 2017-06-19 21:07:53.653467000+0800
Category: Linux | Tags: linux
6
14
2018
2

递归遍历目录:Python vs Go vs Rust

群友提出了一个简单的任务:递归遍历一个很大的目录,根据文件名数一下有多少 JPEG 文件。怎么最快呢?然后他用了 Go 语言实现。

我忽略想起 Python 3.5 的 What's New 里提到,他们优化了 os.scandir 使得目录遍历快了好几倍(PEP 471)。其核心思想是:不进行不必要的 stat 系统调用,因为读目录获得了不少信息,原来都是丢弃掉了,现在改成了通过 DirEntry 对象来返回。这些信息包括文件名等,刚好有我们需要的。

于是 Go 做了这个优化没有呢?

翻了一下代码。Go 自带的实现位于 src/path/filepath/path.go 文件中。可以看到,它对每一个文件都 lstat 了。后来一阁指出,不仅如此,而且它还莫名其妙地对目录下的文件名进行了排序

呃,前者可以说是疏忽了,毕竟 Python 也是直到 3.5 才优化的。可是,它排那个序干嘛呢……

然后我又想到,Rust 那边如何呢?

结果是,Rust 对它所包含的东西非常审慎,标准库里并没有递归遍历目录的函数。那我们自己写一个?才不呢,用第三方库啦!可以看到,它也是返回 DirEntry 对象的。

后来了解到,Go 也有一个第三方的实现 godirwalk,对这些细节进行了优化。

光是了解实现不够。我们让它们来比试一下吧。顺便,把 find 和 fd 也拖进来好了。

任务:数一数一个拥有近万文件的目录下有多少 JPEG 文件。

实现代码:walkdir-test

结果:

   Rust: top:    4.78, min:    4.72, avg:    4.90, max:    5.46, mdev:    0.17, cnt:  20
 Go_3rd: top:    7.71, min:    7.64, avg:    7.79, max:    8.41, mdev:    0.16, cnt:  20
   find: top:   11.49, min:   11.32, avg:   11.76, max:   14.18, mdev:    0.59, cnt:  20
     fd: top:   18.17, min:   15.18, avg:   21.29, max:   29.94, mdev:    3.84, cnt:  20
     Go: top:   21.08, min:   20.91, avg:   21.28, max:   22.70, mdev:    0.37, cnt:  20
 Python: top:   29.66, min:   29.51, avg:   30.43, max:   35.84, mdev:    1.45, cnt:  20
Python2: top:   30.37, min:   30.10, avg:   30.85, max:   33.15, mdev:    0.75, cnt:  20

Rust 如预期一样是最快的。Go_3rd 就是那个第三方库的实现,也非常快的。fd 是 Rust 实现的,目标之一是快,但是这次并没有比老牌的 find 快。Go 自带的那个实现,十分令人遗憾地连 find 都没比过呢,不过还是比 Python 快了不少。Python 2 这次终于没有跑在 Python 3 前边了(虽然差距很小),我猜是 PEP 471 那个优化的功劳。

对了,还有代码行数:

  15 Python/walk
  29 Rust/src/main.rs
  30 Go/walk.go
  33 Go_3rd/walk.go

Rust 竟然不是最长的。不过确实是字符数最多的。

话说 Go 的 } 竟然也是有规定的,结构体的不能另起一行写,只能跟 Lisp 的风格那样堆在一行的尾巴里。

PS: 没想到之前给 swapview 写的 benchmark 程序在另外的项目里用上了呢,果然写东西还是通用些的好。


更新:在群友的提示下,我找了一个更大的目录来测试,结果很不一样呢。这次遍历的目录是 /usr,共有 320397 个文件。

     fd: top:  265.80, min:  259.84, avg:  273.89, max:  319.76, mdev:   15.03, cnt:  20
   Rust: top:  269.98, min:  266.86, avg:  272.82, max:  282.84, mdev:    4.17, cnt:  20
 Go_3rd: top:  361.17, min:  359.05, avg:  363.82, max:  370.22, mdev:    3.31, cnt:  20
   find: top:  454.03, min:  450.79, avg:  458.51, max:  467.31, mdev:    5.08, cnt:  20
 Python: top:  624.80, min:  615.73, avg:  630.67, max:  640.88, mdev:    6.79, cnt:  20
     Go: top:  890.03, min:  876.98, avg:  910.63, max:  967.14, mdev:   24.84, cnt:  20
Python2: top: 1171.38, min: 1157.19, avg: 1189.99, max: 1228.09, mdev: 4186.28, cnt:  20

可以看到,唯一的并行版本 fd 胜出了~Rust 版本紧随其后,显然在此例中并行并没有多么有效。Go_3rd 还是慢于 Rust 但也并不多。然后,经过优化的 Python 终于在更大的数据量上明显胜过了 Go 以及 Python 2 这两个浪费了很多系统调用的版本。

Category: 编程 | Tags: python go 编程语言 Rust
5
25
2018
72

这个博客要死了

很早的时候,那个时候我还在读大学,博客火了一阵。

那时候我刚拥有自己的计算机没多久。那时候我才开始在 Linux 系统里摸索着自学编程。犹犹豫豫地,我注册了一个 WordPress 账号。过了一两周的样子,我那个只有「Hello World」的博客随着众多 WordPress 博客一起被墙了。

那么就换一个地方吧。没过多久,我开通了 Google 提供的 blogger 博客。次日被墙。

那个时候,在国内的网站上发布内容还不需要提供手机号,位于国内的网站也不需要备案。所以我找了个国内的服务,也就是由当时我经常上的 Ubuntu 中文论坛所提供的博客服务。我在那里写过几篇不成熟的文章。过了挺长一段时间的。后来,他们宣布中止服务。

我后来就来到了这里,is-programmer.com。那时候这里还挺繁荣的,有好多人在这里写博客。那时候也没有多少垃圾评论。就这么安静地过了很长时间,站长突然联系不上了。再接着就是用户越来越少,垃圾评论越来越多。我挺担心哪天域名过期,然后自己的博客就消失了。还好我之前向站长申请过自定义域名,于是就用上了。使用 JavaScript 做了跳转和替换,Google 说这样子也是可以的,不过百度那边显然不认可这种做法。后来我又担心这VPS到期没人续费。还好这些都没有发生,域名和VPS都还活着。当然为了以防万一我写了个爬虫,把博客数据爬回来做了备份。

再后来,博客挂了差不多一天。我想方设法登录到了服务器上,删除了大量缓存和日志文件,腾出来些磁盘空间,这才又恢复。然后顺便把之前上传的文件也备份了回来,把域名跳转改成了通过301重定向的方式。不过很可惜,百度依旧不懂。

就这样勉强活着吧。我本来想自己写一个博客程序,可生活实在是太累了,就一直也没有写出来。也不太想用静态博客,因为不方便评论。特别是 Disqus 曾经用欺骗的方式拿了我的社交账号又要求注册 Disqus 账号,以及后来默认不允许匿名评论、匿名评论不显示头像,使得我对 Disqus 挺反感的。

而就在前几天,我这博客被入侵了。入侵者篡改了首页。虽然刷新缓存之后就恢复了,但我感觉很不好。

这博客,已经老了。就算我通过各种打补丁的方式,支持 HTTPS,支持 IPv6,更换自己控制的域名,使用脚本更改页面中的旧链接,但是,它还是在一点一点、不可避免地衰老。

Category: 未分类 | Tags:

部分静态文件存储由又拍云存储提供。 | Theme: Aeros 2.0 by TheBuckmaker.com