MPI 上基于 RMA 的并行互斥锁和读写锁

MPI2 支持简单的 RMA 操作, 尤其是 Passive 模式, 取数据的进程不需要目标进 程干预, 在支持互斥锁的 Passive 模式下, 可以实现一个并行全局计数器. 这在 Using MPI 2 1 里, 有比较详细的实现. 书中还提到基于此计数器实现分布互斥锁, 并给出了代码.
void MPE_Mutex_lock_simple(MPI_Win win) {
  int value;
  MPE_Counter_inc_simple(win, &value, 1);
  while (value != 0) {
    MPE_Counter_inc_simple(win, &value, -1);
    MPE_Counter_inc_simple(win, &value, 1);
  }
}

void MPE_Mutex_unlock_simple(MPI_Win win) {
  int value;
  MPE_Counter_inc_simple(win, &value, -1);
}
以上代码有严重的效率问题2. 在进程数多了以后, 计数器的期望在进程数一半 处,只有非常小的概率在零附近, 从而使各进程不断的在 while 循环里. 简单 的加一个计数器是否大于零的测试再尝试加一, 减一的循环应该可以减少在 =while=循环里的进程数, 从而使计数器的值有较大概率在零附近.
class comm_mutex {
 public:
  comm_mutex(Comm &comm)
      : m_counter(comm) {
  }

  void lock() {
    while (m_counter.add(1) > 1) {
      if (m_counter.add(-1) > 0) {
        while (m_counter.get() > 0);
      }
    }
  }

  void unlock() {
    m_counter.add(-1);
  }

 private:
  comm_counter m_counter;
};
分析两种实现的效率, 即 $n$ 个进程同时加锁, 马上释放, 给出计数器 add, get 的效率后, 估算两种实现从第一个进程进锁, 到最后一个进程离锁的时间. 为简化, 我们假设各 add,get 的时间是固定的, 不随计数器的访问进程数 变化, 且进一步假设 get 的时间和 add 一样. 我们还假设计数器的互斥访 问是随机的. 最后, 我们需要看, 到最后一个进程释放锁时, 访问计数器的总次 数.
在第一个片段里, 标记程序在 unlock 里减一前为状态 L, 减一后, 为状态 R. 在 lock 里, 加一前为状态 A, 减一前为状态 S. 分别以 $l, r, a, s$ 记 某一时刻在某一状态的程序数目. 计数器的值为 $l + s$, 且 $l$ 只能为 0 或 1. 可以容易得到程序集从状态 $(l, r, a, s)$ 进一次计数器变换到另一个 状态的概率. 如从 $(l, r, a, s)$ 到 $(l, r, a + 1, s - 1)$ 的概率为 $\frac{s}{l + a + s}$.
问题即是, 程序集从状态 $(0, 0, n, 0)$ 到状态 $(0, n, 0, 0)$ 的转移次数 的期望. 有转移概率, 即可在程序上求此数值, 但表达形式应该比较复杂, 不知 道极限有没有简单的形式. 从 $s$ 的期望近似为 $\frac{n - r}{2}$ 看, 转移 次数期望有关于 $n$ 的指数下限. 实测时, 在进程数到 $32$ 后, 普遍会在释放 一两个锁后, 僵锁在加一, 减一的循环里.
第二个片段类似. 标记程序在 unlock 里减一前为状态 L, 减一后, 为状态 R. 在 lock 里, 加一前为状态 A, 减一前为状态 S, get 前为状态 G, 分别 以 $l, r, a, s, g$ 记某一时刻在某一状态的程序数目. 问题变成程序集从状态 $(0, 0, n, 0, 0)$ 到状态 $(0, n, 0, 0, 0)$ 的转移次数的期望.
$s$ 大后, 多数程序会转移到状态 G, 从而 $s$ 的期望为一个比较小的常数,从 而每次进锁的程序只需要常数次测试, 如是, 转移次数期望应有上限 $O(n^2)$. 实测显示, 第 $i$ 个进锁的进程大概需要 $3i$ 次 add, get, 与前一个的差为常值 $3$.
用类似的方式, 可实现读写锁, 最简单的是加一个计数器. 在加读锁时, 按上面 方式, 加一, 减一循环, 使写锁计数器到一, 加读计数器, 减写锁计数器; 在加 写锁时, 使写锁计数器到一, 自旋至读计数器为零.

Footnotes:

2 我没找到非 simple 版的 MPE_Mutex_lock.
3 blogger 似乎不支持使用 Content-ID 资源的HTML邮件, 只得取消了 latex 图片展示.

部分重写密码软件

不知道是左脚站在右脚上, 还是右脚站在左脚上, 我们只知道他站在自己的脚趾上.

自上次初学 Python 写一个自己用的密码管理软件 pgman 已经有一年半了, 存在 的问题有:

  • 交互程序, 不方便其它程序使用.

    比如, 有一个脚本, 初始化所有加密分区. 需要在问密码时, 切到这个程序, 调出密码到剪贴板, 再切过来, 粘贴密码.

  • 密码明文存在内存里, 没有锁住, 容易被交换到磁盘, 造成泄漏.

    这个问题, 其实在目前的是没法严格解决的. 不说密码管理软件, 单使用密码 中, 流程太长, 就如浏览器等, 未必在释放内存前清空密码. 但, 在密码管理 软件中, 少一层不必要的泄漏, 总好一层.

  • 访问安全问题.

    人走时, 未必记得退出此程序, 为方便, 也总是常开着. 虽然设有密码遗忘时 间. 但在别人可访问电脑时, 用 GDB 或 其它内存分析工具可完全获得所有密 码.

五一短假, 本来想用还没学会的 ErLang 练手写一个, 但 ErLang 上手不易, 以 上的问题还是无从下牙. 最后, 还是用 C 写了个.

设计上是, 使用会话加密. 会话私钥不加密地放在 U 盘里, 会话公钥放机器上. 有一个服务程序, 加载原来 pgman 的文件, 这个需要询问永久存储的私钥密码, 用会话公钥加密敏感信息, 保留结构, 存在内存里. 另有一个客户端(现在主要功 能是 BASH 脚本写的), 自动检查并挂载 U 盘, 查询信息, 解密. 服务端返回的 信息, 除状态码外, 都是加密的. RSA 块加密有一个好处是加密信息可以自然串 联起来, 而不必解密.

目前只实现读, 还没有实现写. 写需要在服务端做解密, 写用得不多, 移时再 说.

关于 RSA 加密, 测试显示, OpenSSL 的实现, 4096 位的密码操作时间是 512 位 的 120 倍, 与我所理解的 8 log(8) = 24 倍差太多了, 可能的原因是加密 的主要是短字符串, 4096 位浪费的较多.

下次等熟悉后, 考虑买一个 USB-KEY 吧, 不这样山寨了.

一湖春景

宅, 有外出玩的意向, 无咎.

三月即至, 和打油诗, 以忆未名湖春景.

垂柳拂枝怜桃花, 游人临池怜塔影. 最恨人间仲春景, 无风无雨也无晴.



2 * 2 * 3 * 277 * 6047

SWIG 磨人

每次用新工具, 我都要觉得这东西不够灵活.

SWIG 自1995 年开始, 至今也算稳定了. 只是稳定到一个不够 versatile 的状态.

SWIG的Typemap很方便, 只可惜, 找不到将多参数一起解析, 或者解析时引用前面参数的方法. 文档里倒是明确提到 numinputs 须为0或1, 像是不支持这个.

文档里找不到, 邮件列表里没人解答, 虽然还没到最后一步看实现, 估计我也放弃了. 有新资源申请依赖参数时, 手写Python扩展也比这个框架下写接口来得快.

``SWIG垃圾, 支持多语言的自然做不好.''

``框架!''

虽然, 倒是从 numpy.i 里找出一些代码, 包装数组挺方便的.

纯属测试: 中共匪然海外的都与敏感词相关么.

几种简单的排序算法比较次数比较

几种简单的排序算法比较次数比较

1 说明

几种排序算法, 有一些是简单实现, 另有 glibc 的 qsort 和 GNU C++ 库的 std::sort. 下面的时间是排序 long 型数组的时间. 其实, 是一个 C 里 Sequence, 但此 Sequence 占用的时间和时间的误差相近, 在 10% 左右.

比较程序使用 random 生成数组, 在数组比较小时, 排序并打乱数组多次, 取时 间平均值, 最后几个, 因时间比较大, 只比较一次. 所以, 在所有的时间里, 有 打乱数组的时间开销, 大致为 O(size * time(random)). 本文只关心相对比较明 显的差异, 故不细分这些.

各排序算法如其名. 其中 quicksort_nr 是快速排序的非递归版, cqsort 是调 C 标准库函数 qsort. std::sort(iterator) 中的 get/set 不能区分, 统一计在 get 下.

C 的 qsort 由于必须有函数指针, 比较是相对较大开销, 所以在数组较小, 且内 存足够时采用需要额外内存但比较次数最少的归并排序. C++ 采用的是 quicksort 和 heapsort 的杂交 introsort, 比较次数相对归并排序多一点, 但 对整数而言, 比较仅是减法, 受限于内存访问的时间, 估可以次要考虑.

2 sort comparisons table


Algo.Sizetime# comparisons/size# gets/size# sets/size
introsort2^52.29932e-065.274929.386234.00063
heapsort2^52.70095e-065.1821813.22918.45347
mergesort2^52.37965e-063.796959.296956
quicksort2^52.19414e-065.560798.3592.83799
quicksort_nr2^52.15747e-065.369258.235582.85841
cqsort2^52.98512e-063.79623NANA
combsort2^52.41381e-068.8018417.61233.36478
std::sort(iterator)324.68653e-065.9521813.9444NA
std::sort321.65281e-065.94951NANA
introsort2^64.42774e-066.5157111.08174.47673
heapsort2^65.82915e-066.2620715.3059.46844
mergesort2^64.97256e-064.7661510.26626
quicksort2^64.87501e-066.7783410.05663.35011
quicksort_nr2^64.76849e-066.602369.942053.3574
cqsort2^66.40913e-064.76535NANA
combsort2^65.18118e-0611.609423.22344.28761
std::sort(iterator)643.99712e-067.1153315.4967NA
std::sort643.62354e-067.12488NANA
introsort2^71.00313e-057.7648812.82934.99319
heapsort2^71.28292e-057.3085817.346110.4755
mergesort2^71.94854e-055.7494213.24948
quicksort2^71.06718e-057.9855111.7373.83951
quicksort_nr2^71.05284e-057.8048811.6143.8471
cqsort2^71.41386e-055.74657NANA
combsort2^71.22646e-0514.512629.02745.30448
std::sort(iterator)1288.65036e-068.3236917.1383NA
std::sort1287.87806e-068.31551NANA
introsort2^82.16346e-058.9674514.52865.49551
heapsort2^82.79746e-058.3332119.368211.4748
mergesort2^82.38339e-056.7419714.2428
quicksort2^82.26148e-059.2145413.43754.31297
quicksort_nr2^82.29063e-059.0021713.29554.34143
cqsort2^83.07402e-056.74541NANA
combsort2^82.71602e-0517.38334.76726.34737
std::sort(iterator)2561.99121e-059.5476518.8368NA
std::sort2561.73477e-059.57421NANA
introsort2^94.58946e-0510.155616.22476.01352
heapsort2^96.01988e-059.3549621.392212.4821
mergesort2^95.17732e-057.7391117.23911NA
quicksort2^94.92418e-0510.361815.06154.8
quicksort_nr2^94.81522e-0510.183314.95124.81563
cqsort2^96.54729e-057.74022NANA
combsort2^95.884e-0520.352140.70477.38508
std::sort(iterator)5124.32935e-0510.795420.6048NA
std::sort5123.68822e-0510.7974NANA
introsort2^100.00010043811.358217.93566.51449
heapsort2^100.00012830510.36123.397413.4805
mergesort2^100.0001068048.7383318.238310
quicksort2^100.00010464911.575716.74615.26775
quicksort_nr2^100.00010298411.430216.65645.27142
cqsort2^100.0001853988.73907NANA
combsort2^100.00014446923.950847.90198.49806
std::sort(iterator)10248.9258e-0511.954622.1866NA
std::sort10247.89147e-0512.0618NANA
introsort2^110.00023065512.532219.69557.09028
heapsort2^110.00028826711.366225.402514.4817
mergesort2^110.0002396899.736421.236412
quicksort2^110.0002410312.732318.37895.75391
quicksort_nr2^110.00021754612.538318.26075.76871
cqsort2^110.0003054819.737NANA
combsort2^110.00028974927.091954.1849.64027
std::sort(iterator)20480.00019089113.173523.8782NA
std::sort20480.0001666413.1915NANA
introsort2^120.00046250213.841621.60967.6772
heapsort2^120.00057006612.3727.40515.4825
mergesort2^120.00051040910.736522.236512
quicksort2^120.00046962513.945920.08336.24193
quicksort_nr2^120.0004639113.70619.90276.24634
cqsort2^120.00064265710.7388NANA
combsort2^120.00063899930.262260.524510.6047
std::sort(iterator)40960.00040534114.465525.6454NA
std::sort40960.00035872314.6613NANA
introsort2^130.00094431614.980323.25488.18111
heapsort2^130.001234513.372829.409616.483
mergesort2^130.0011129411.736625.236614
quicksort2^130.0010070715.123421.73036.71223
quicksort_nr2^130.00099806514.859921.5356.73174
cqsort2^130.0013335511.7354NANA
combsort2^130.0014358233.842267.684511.7082
std::sort(iterator)81920.00086368615.767427.4121NA
std::sort81920.0007462515.6189NANA
introsort2^140.0021051216.12624.96778.75029
heapsort2^140.0026205214.369431.403517.4816
mergesort2^140.0023129912.738226.238214
quicksort2^140.0024223616.135823.23437.2082
quicksort_nr2^140.0023941416.129923.26887.187
cqsort2^140.0029565112.7369NANA
combsort2^140.0031642637.091274.182512.864
std::sort(iterator)163840.0018147517.111929.2536NA
std::sort163840.0016003817.1486NANA
introsort2^150.0050492317.369626.83739.35675
heapsort2^150.0056817515.364833.398918.474
mergesort2^150.0054972813.733129.233116
quicksort2^150.0046777717.763325.28967.6303
quicksort_nr2^150.0050014917.266224.89327.67592
cqsort2^150.0065320113.7351NANA
combsort2^150.007007340.216780.433413.9531
std::sort(iterator)327680.0038477818.333830.9398NA
std::sort327680.0033502617.9507NANA
introsort2^160.0096925518.517228.58199.94678
heapsort2^160.011969116.361935.397519.4703
mergesort2^160.010597514.732930.232916
quicksort2^160.0094070418.323526.40238.18502
quicksort_nr2^160.0095629718.106626.24758.1935
cqsort2^160.012993514.7351NANA
combsort2^160.014304543.214286.428415.0652
std::sort(iterator)655360.008198519.551932.6143NA
std::sort655360.007059119.4078NANA
introsort2^170.019830919.252129.675810.3184
heapsort2^170.02646817.355437.389520.462
mergesort2^170.024387815.734633.234618
quicksort2^170.02496719.918428.44278.62446
quicksort_nr2^170.020683119.672828.28098.6527
cqsort2^170.02870815.7364NANA
combsort2^170.03145146.213292.426316.2224
std::sort(iterator)1310720.01720620.735934.2805NA
std::sort1310720.014752920.7001NANA
introsort2^180.042837120.776331.991711.08
heapsort2^180.06022518.35439.387821.461
mergesort2^180.05420416.735434.235418
quicksort2^180.044906120.932329.95069.11972
quicksort_nr2^180.044407120.791129.85849.11888
cqsort2^180.061237116.7355NANA
combsort2^180.070429149.212798.425317.2837
std::sort(iterator)2621440.036987121.805635.8416NA
std::sort2621440.033554121.7911NANA
introsort2^190.1053122.116633.975611.7039
heapsort2^190.16923119.35441.387622.4603
mergesort2^190.12893617.736237.236220
quicksort2^190.10564522.126731.60429.58018
quicksort_nr2^190.10500522.23431.77779.59514
cqsort2^190.15018217.7361NANA
combsort2^190.18412852.2115104.42318.3483
std::sort(iterator)5242880.094221123.330637.815NA
std::sort5242880.085955124.1802NANA
introsort2^200.24776423.447536.075412.4453
heapsort2^200.44266320.354543.388723.4609
mergesort2^200.29055618.73638.23620
quicksort2^200.25062623.241233.207410.0712
quicksort_nr2^200.25393123.307133.318110.0623
cqsort2^200.34422118.736NANA
combsort2^200.4308656.2095112.41919.3904
std::sort(iterator)10485760.23045923.955438.9847NA
std::sort10485760.2057224.3193NANA
introsort2^210.55471224.174837.159812.82
heapsort2^211.0754921.354445.388924.4613
mergesort2^210.65845219.736141.236122
quicksort2^210.56490924.339434.798310.5612
quicksort_nr2^210.55677324.050934.580810.582
cqsort2^210.75741919.7364NANA
combsort2^210.97694859.2084118.41720.509
std::sort(iterator)20971520.50442225.502140.9916NA
std::sort20971520.47248925.5631NANA
introsort2^221.2034625.508839.172713.4783
heapsort2^222.4821322.354247.388525.4608
mergesort2^221.3851220.735742.235722
quicksort2^221.208825.605936.527511.0243
quicksort_nr2^221.1937125.868336.81310.995
cqsort2^221.563720.7359NANA
combsort2^222.0283262.2124124.42521.6898
std::sort(iterator)41943041.0736826.534242.514NA
std::sort41943040.97738226.8222NANA
introsort2^232.4810726.994841.425114.2166
heapsort2^235.7018423.353949.388126.4607
mergesort2^232.9349221.735745.235724
quicksort2^232.5393726.489637.922611.5348
quicksort_nr2^232.5229828.030939.391311.4106
cqsort2^233.2848121.7354NANA
combsort2^234.399466.2117132.42322.7599
std::sort(iterator)83886082.2389927.888244.3053NA
std::sort83886082.0583327.8466NANA
introsort2^245.2841128.792744.279615.2239
heapsort2^2412.959524.354151.388327.4607
mergesort2^245.9699422.735746.235724
quicksort2^245.1942527.937639.818211.9814
quicksort_nr2^245.2373527.937639.857311.9697
cqsort2^246.7785722.7356NANA
combsort2^248.8429169.2087138.41723.8582
std::sort(iterator)167772164.7065928.79545.7217NA
std::sort167772164.2997529.2741NANA

對接口編程, 不要對結構編程 ---- Python 經驗對 C 編程的考慮

通常, 如果幾行SHELL可以搞定, 不會用 Python. 幾行 Python 可以搞定的, 也就懶得用 C 了.

Python 最好用的是語言級的 list 和 dict 支持. 還有如 generator 可以帶來另外的編程範式, C 裏類似的效果, 常伴隨理解困難和BUG. 另外一個是Python的文件模塊化, 對象抽象和各種類似于Decorator的東西, 不僅是編程方便, 也提供了編碼工程上必須的分離. lambda 表達式也是 C 裏尚沒有的.

前時, 泛讀了 C Interfaces and Implementations (是的, 窮人的電子版), 突然對以前在哪看過的一句話非常贊同起來: 編程, 對外暴露接口, 不要暴露數據結構. 如此相反的觀點, 任何抽象都太重, 曾經左右我.

如果能接受腳本語言, 自然能接受C語言裏抽象的接口, 而隱藏數據結構. Python 的方便其實也自此而來.

Python 相比 C 的問題主要是運行慢, C 相比 Python 的問題主要是編碼慢, 易錯, 調試難, 缺泛強大的標准庫.

如果有熟悉的 LIST, Hash-Table, AVL, Sequence 的實現, 熟悉到可以像 Python 的 list, dict 那樣隨手拈來, 用 C 編碼也是很快的. 這些數據結構的通用實現, 自然要求隱藏數據結構. 這樣, 只要接口不變, 你可以方便的換一個實現, 只是需要重新編譯, 或者, 再加一層, 用來轉發到具體的實現, 像在很多 C++ 代碼裏見到的 FooInterface, FooImpl.

喜歡 Generator 的人不好應付一些. C 裏有一些 coroutine 的庫, 但在各堆棧跳來跳去是此編程方式的BUG發源地. 用事件狀態機加多線程是C的經典且易理解的編程方式, 我放棄協程這一條, 就像 Lambda 一樣.

程序, 大部分處理數據, 部分生成數據(處理需要理解, 比生成困難), 小部分是生成不可預測的數據, 也就是 BUG 隱藏地和隨機數的生成地.

在所有的調試方法裏, 某些人偏好 printf 和 assert. 對一個函數的前件和後件多作一些 assert, 很多BUG可以不寫測試也能發現. 程序正確性証明的一個方法就是証明不變量, 對不變量作 assert, 就會c對程序的正確性自信得多.

半年用 Python 的經驗是 Python 更容易寫錯誤的代碼, 多半是運行時才會發現. 只是Python會打印一個錯誤堆棧和出錯位置, C 裏同樣的功能可以設置 core 文件大小限制來得到.

昏黄的颜色

颜色能引起回忆, 比我清晰记得的更多. 那一瞬, 仿佛又回到那时, 那时, 你会与我挣抢昏黄煤油灯的光.