快速范围判断:再来一种新写法

June 10th, 2020 No comments

C 语言的魔法数不胜数,我在《C 语言有什么奇技淫巧?》中过给快速范围判断的公式,将:

if (x >= minx && x <= maxx) ...

改做:

if (((x - minx) | (maxx - x)) >= 0) ...

能有一倍的性能提升,我也提到,如果你的数据 99% 都是超出范围的那继续用 && 最快。今天再给大家介绍另外一种新写法,它有更均衡的性能,并且在最坏的情况下,任然表现良好:

if ((unsigned)(x - minx) <= (unsigned)(maxx - minx)) ...

该公式在各种测试数据中能有更均衡的表现,类型安全狂们可以写作:

if (((unsigned)x - (unsigned)minx) <= ((unsigned)maxx - (unsigned)minx)) ...

利用单次无符号整数溢出来减少指令和分支,普通情况,这个公式性能照样快接近一倍:

链接:http://quick-bench.com/EbCR9psA3lUEhpn8bYLwVtJ-FWk

为什么说它综合性能最好呢?是不是只实用于某些特殊情况呢?普通情况如何?汇编指令有啥区别?理论依据是啥?是不是只有 x86 可以用,换个平台就不行呢?下面依次回答:

(点击 Read more 展开)

Read more…

Categories: 编程技术 Tags:

C 语言有什么奇技淫巧?

May 29th, 2020 No comments

C 语言的技巧有很多,列一些和性能有关的:

快速范围判断

经常要批量判断某些值在不在范围内,如果 int 检测是 [0, N) 的话:

if (x >= 0 && x < N) ...

众所周知,现代 CPU 优化,减分支是重要手段,上述两次判断可以简写为:

if (((unsigned int)x) < N) ...

减少判断次数。如果 int 检测范围是 [minx, maxx] 这种更常见的形式的话,怎么办呢?

if (x >= minx && x <= maxx) ...

可以继续用比特或操作继续减少判断次数:

if (( (x - minx) | (maxx - x) ) >= 0) ...

如果语言警察们担心有符号整数回环是未定义行为的话,可以写成这样:

if ((int32_t)(((uint32_t)x - (uint32_t)minx) | ((uint32_t)maxx - (uint32_t)x)) > = 0) ...

性能相同,但避开了有符号整数回环,改为无符号回环,合并后转为有符号判断最高位。

第一个 (x – minx) 如果 x < minx 的话,得到的结果 < 0 ,即高位为 1,第二个判断同理,如果超过范围,高位也为 1,两个条件进行比特或运算以后,只有两个高位都是 0 ,最终才为真,同理,多个变量范围判断整合:

if (( (x - minx) | (maxx - x) | (y - miny) | (maxy - y) ) >= 0) ...

这样本来需要对 [x, y] 进行四次判断的,可以完全归并为一次判断,减少分支。

补充:加了个性能评测

性能提升 37%。快速范围判断还有第二个性能更均衡的版本:

if ((unsigned)(x - minx) <= (unsigned)(maxx - minx)) ...

快速范围判断的原理和评测详细见:《快速范围判断:再来一种新写法》。

更好的循环展开

很多人提了 duff’s device ,按照 gcc 和标委会丧心病狂的程度,你们用这些 just works 的代码,不怕哪天变成未定义行为给一股脑优化掉了么?其实对于循环展开,可以有更优雅的写法:

#define CPU_LOOP_UNROLL_4X(actionx1, actionx2, actionx4, width) do { \
    unsigned long __width = (unsigned long)(width);    \
    unsigned long __increment = __width >> 2; \
    for (; __increment > 0; __increment--) { actionx4; }    \
    if (__width & 2) { actionx2; } \
    if (__width & 1) { actionx1; } \
}   while (0)

送大家个代替品,CPU_LOOP_UNROLL_4X,用于四次循环展开,用法是:

(点击 Read more 展开)

Read more…

Categories: 编程技术 Tags:

支持 Win10 的网络环境模拟(丢包,延迟,带宽)

April 13th, 2020 No comments

升级 Windows 10 以后,原来各种网络模拟软件都挂掉了,目前能用的就是只有 clumsy

唯一问题是不支持模拟带宽,那么平时要模拟一些糟糕的网络情况的话,是不太方便的,而开虚拟机用 Linux tc 或者设置个远程 linux 网关又很蛋疼,于是我顺便给他加了个带宽模拟功能:

注意最下面的 “Bandwidth” 选项,打上勾的话,就能顺利限速了,注意上面的 Filtering 需要填写正确的 WinDivert 规则。

注意,统计包大小时用的是整个 IP 包的大小(包括各种协议头),所以你设置成 500 KB/s 的话,实际按 tcp 计算的下载速率会略小。

二进制下载:

想自己检查自己编译的话:

欢迎 PR。

Categories: 网络编程 Tags:

用好你的瑞士军刀/netcat

September 24th, 2019 No comments

Netcat 号称 TCP/IP 的瑞士军刀并非浪得虚名,以体积小(可执行 200KB)功能灵活而著称,在各大发行版中都默认安装,你可以用它来做很多网络相关的工作,熟练使用它可以不依靠其他工具做一些很有用的事情。

最初作者是叫做“霍比特人”的网友 Hobbit hobbit@avian.org 于 1995 年在 UNIX 上以源代码的形式发布,Posix 版本的 netcat 主要有 GNU 版本的 netcat 和 OpenBSD 的 netcat 两者都可以在 debian/ubuntu 下面安装,但是 Windows 下面只有 GNU 版本的 port。

不管是程序员还是运维,熟悉这个命令都可以让很多工作事半功倍,然而网上基本 90% 的 netcat 文章说的都是老版本的 OpenBSD 的 netcat,已经没法在主流 linux 上使用了,所以我们先要检查版本:

在 debian/ubuntu 下面:

readlink -f $(which nc)

看看,结果会有两种:

  • /bin/nc.traditional: 默认 GNU 基础版本,一般系统自带。
  • /bin/nc.openbsd: openbsd 版本,强大很多。

都可以用 apt-get install nc-traditional 或者 apt-get install nc-openbsd 来选择安装。不管是 gnu 版本还是 openbsd 版本,都有新老的区别,主要是传送文件时 stdin 发生 EOF 了,老版本会自动断开,而新的 gnu/openbsd 还会一直连着,两年前 debian jessie 时统一升过级,导致网上的所有教程几乎同时失效。

下面主要以最新的 GNU 版本为主同时对照更强大的 openbsd 版本进行说明。

端口测试

你在服务器 A主机(192.168.1.2) 上面 8080 端口启动了一个服务,有没有通用的方法检测服务的 TCP 端口是否启动成功?或者在 B 主机上能不能正常访问该端口?

进一步,如果而 A 主机上用 netstat -an 发现端口成功监听了,你在 B 主机上的客户端却无法访问,那么到底是服务错误还是网络无法到达呢?我们当然可以在 B 主机上用 telnet 探测一下:

telnet 192.168.1.2 8080

但 telnet 并不是专门做这事情的,还需要额外安装,所以我们在 B 主机上用 netcat:

nc -vz 192.168.1.2 8080

即可,v 的意思是显示多点信息(verbose),z 代表不发送数据。那么如果 B 主机连不上 A 主机的 8080 端口,此时你就该检查网络和安全设置了,如果连的上那么再去查服务日志去。

nc 命令后面的 8080 可以写成一个范围进行扫描:

nc -v -v -w3 -z 192.168.1.2 8080-8083

两次 -v 是让它报告更详细的内容,-w3 是设置扫描超时时间为 3 秒。

传输测试

你在配置 iptable 或者安全组策略,禁止了所有端口,但是仅仅开放了 8080 端口,你想测试一下该设置成功与否怎么测试?安装个 nginx 改下端口,外面再用 chrome 访问下或者 telnet/curl 测试下??还是 python -m 启动简单 http 服务 ?其实不用那么麻烦,在需要测试的 A 主机上:

nc -l -p 8080

这样就监听了 8080 端口,然后在 B 主机上连接过去:

nc 192.168.1.2 8080

两边就可以会话了,随便输入点什么按回车,另外一边应该会显示出来,注意,openbsd 版本 netcat 用了 -l 以后可以省略 -p 参数,写做:nc -l 8080 ,但在 GNU netcat 下面无法运行,所以既然推荐写法是加上 -p 参数,两个版本都通用。

老版本的 nc 只要 CTRL+D 发送 EOF 就会断开,新版本一律要 CTRL+C 结束,不管是服务端还是客户端只要任意一边断开了,另一端也就结束了,但是 openbsd 版本的 nc 可以加一个 -k 参数让服务端持续工作。

那么你就可以先用 nc 监听 8080 端口,再远端检查可用,然后又再次随便监听个 8081 端口,远端检测不可用,说明你的安全策略配置成功了,完全不用安装任何累赘的服务。

(点击 Read more 展开)

Read more…

Categories: 网络编程 Tags:

最近都流行实现 Coroutine 么 ?

August 10th, 2019 No comments

这两天看着大家都在实现无栈的 coroutine 都挺好玩的,但无栈协程限制太多,工程实践上很少用,所以昨天手痒写了个有栈的 coroutine ,接口反照 ucontext 的接口,不比无栈的复杂多少:

int main(void)
{
    ctx_context_t r;
    int hr;
    volatile int mode = 0;

    hr = ctx_getcontext(&r);
    printf("ctx_getcontext() -> %d\n", hr);

    if (mode == 0) {
        mode++;
        printf("first run\n");
        ctx_setcontext(&r);
    }
    else {
        printf("second run\n");
    }
    printf("endup\n");

    return 0;
}

使用 ctx_getcontext / ctx_setcontext 可以实现保存现场,恢复现场的功能,该程序输出:

ctx_getcontext() -> 0
first run
ctx_getcontext() -> 6356604
second run
endup

继续使用 ctx_makecontext / ctx_swapcontext 可以实现初始化协程和切换上下文的功能:

char temp_stack[32768];
ctx_context_t mc, cc;

int raw_thread(void*p) {
    printf("remote: hello %s\n", (char*)p);
    ctx_swapcontext(&cc, &mc);

    printf("remote: back again\n");
    ctx_swapcontext(&cc, &mc);

    printf("remote: return\n");
    return 1024;
}

int main(void)
{
    cc.stack = temp_stack;
    cc.stack_size = sizeof(temp_stack);
    cc.link = &mc;

    ctx_getcontext(&cc);
    ctx_makecontext(&cc, raw_thread, (char*)"girl");

    printf("before switch: %d\n", cc.stack_size);
    ctx_swapcontext(&mc, &cc);

    printf("local: here\n");
    ctx_swapcontext(&mc, &cc);

    printf("local: again\n");
    ctx_swapcontext(&mc, &cc);

    printf("local: end\n");
    return 0;
}

这里创建了一个协程,接着主程序和协程互相切换,程序输出:

before switch: 32768
remote: hello girl
local: here
remote: back again
local: again
remote: return
local: end

关于实现

核心代码其实很简单,就 60 多行,没啥复杂的:

(点击 Read more 展开)

Read more…

Categories: 编程技术 Tags:

如何长时间保存重要数据?

May 9th, 2019 23 comments

我大学毕业时把所有资料刻录成几张 dvd,才几年就发现读取不了了,而我老爸读大学时候的笔记本,几十年后仍保存完好。我前几年保存在移动硬盘里的照片,因为搬家时摔了一次,完全毁坏了,但是我家里小时候的相册却能几十年没有事情。

所以今天数据存储固然比过去更加方便,但是可靠性却大为降低。硬件坏了你还可以花钱再买,数据丢了,你就再也无力回天了。数据对我来讲是最宝贵的东西,无数血与泪的教训后,让我开始深入思考,怎么样才能让我的数据长期安全的保存几十年甚至终身?

可以用光碟么?

光碟是最廉价最受欢迎的介质,他们本来设计寿命是 10-20 年的,而一般情况你不要指望你光盘上的东西五年后还能正常读出来。即便一些号称长期保存百年以上的光盘,寿命也会由于我们各种不当行为大大降低,比如,没法按要求的条件保存(放桌面上被阳光暴晒变形),不小心刮花光盘,在盘面上留下指纹或者手上的油脂,这些都会促进光盘表面化学成分变质,最终导致你的数据损坏。

可以用机械硬盘么?

这两年 HDD/SSD 技术进步很快,成本越来越低。8T 的 HDD 差不多只 1000 元人名币的成本,1T 的 SSD 也从过去的好几千元降价到 600 多了。HDD/SSD 都能组成阵列,用虚拟逻辑卷的形式跨越物理大小的限制,为你提供超大规模的连续存储空间。

然而当你想要维护更大规模的盘阵时,你基础硬件设施的成本会大幅上升,4路阵列和8路16路的成本完全不一样。同时更新换代快,我过去保存的几块 IDE/SATA 接口的硬盘,今天我已经没有任何可用的设备来读取他们了。

遗憾的是,不管是 HDD 还是 SSD 他们都不能长期可靠的保存数据,每年有 1% 的概率由于磁场变化造成 HDD 数据损坏,这个损坏率会随着硬盘寿命逐年变大。而 SSD 的寿命比 HDD 更短,同时他们还会受到温度的影响,如果长期处在40度以上的工作温度,二者的寿命都会减半。

Read more…

Categories: 随笔 Tags:

用 Vim/VsCode 来写 WordPress 博客

May 8th, 2019 3 comments

试用过一段时间各种静态页面博客系统,Hugo 这些,虽然发展的不错,但是比起 WordPress 来还是太弱了。WordPress 毕竟是发展了 15 年的东西各种功能和插件都比较完善。

所以这次回过头来重新使用 WordPress,顺便做了升级,速度更快了(升级 PHP7,引入页面缓存等),代码高亮等各种小功能也调优了一下,又加了一些类似热门文章和访问计数等小功能。

然后我写了一个命令行工具,可以让我在喜欢的文本编辑器里用 MarkDown 写博客,然后命令行发布到 WordPress,具体见 markpress 相关文档。

下面是一些调优后的效果,首先 Markdown 的代码块,使用 highlight.js 以后好看很多:

#include <stdio.h>
int main(void)
{
    printf("Hello, World !!\n");
    return 0;
}

这个插件支持 185 种语言(包括 Vim)的高亮,可以选择 89 种主题,是目前最强的代码高亮解决方案。

MarkPress 页面生成基本尊崇 Github 规范:

  1. 连接会被自动识别,只需要直写 URL,就会自动识别出来加上 <a> 标签。
  2. 比如双波浪线包围的内容 ~~测试~~ 会被划掉显示为:测试
  3. 比如 Github Emoji,直接写 :smile: 的 shortcode,就会变成 :smile:

除此之外还有很多 github 没有的扩展,比如:

折叠菜单点击左边箭头打开

第一行隐藏的折叠内容
第二行隐藏的折叠内容

MathJax 的内嵌公式,被 $ 符号包围住的内容会被识别成 latex 公式:

$z=\sqrt{x^2 + \sqrt{y^2}}$

得到:

$z=\sqrt{x^2 + \sqrt{y^2}}$

然后是 GraphViz 图形,现在在 MarkDown 中用 viz-{引擎名称} 开头的代码块:

```viz-dot
digraph G {
   A -> B
   B -> C
   B -> D
}
```

能被转换为内嵌 SVG 代码,并被主流浏览器正常显示:

Read more…

Categories: 随笔 Tags:

kNN 的花式用法

April 17th, 2019 1 comment

kNNk-nearest neighbors)作为一个入门级模型,因为既简单又可靠,对非线性问题支持良好,虽然需要保存所有样本,但是仍然活跃在各个领域中,并提供比较稳健的识别结果。

说到这里也许你会讲,kNN 我知道啊,不就是在特征空间中找出最靠近测试样本的 k 个训练样本,然后判断大多数属于某一个类别,那么将它识别为该类别。

这就是书上/网络上大部分介绍 kNN 的说辞,如果仅仅如此,我也不用写这篇文章了。事实上,kNN 用的好,它真能用出一朵花来,越是基础的东西越值得我们好好玩玩,不是么?

第一种:分类

避免有人不知道,还是简单回顾下 kNN 用于分类的基本思想。

针对测试样本 Xu,想要知道它属于哪个分类,就先 for 循环所有训练样本找出离 Xu 最近的 K 个邻居(k=5),然后判断这 K个邻居中,大多数属于哪个类别,就将该类别作为测试样本的预测结果,如上图有4个邻居是圆形,1是方形,那么判断 Xu 的类别为 “圆形”。

第二种:回归

根据样本点,描绘出一条曲线,使得到样本点的误差最小,然后给定任意坐标,返回该曲线上的值,叫做回归。那么 kNN 怎么做回归呢?

Read more…

Categories: 人工智能 Tags:
Wordpress Social Share Plugin powered by Ultimatelysocial