最近老碰到当前窗口被抢焦点,却不知道是哪个程序抢了焦点,找到了这一篇文章: 继续阅读
刚看到,intel tbb::pipeline 实现的功能,和我以前实现的一个pipeline : febird::thread::PipelineProcessor
,介绍:
几乎就是同一个东西:多线程流水线执行,按次序,stage划分……
可惜啊,它只在我的几个程序中用过,现在有了tbb,它还没来得及壮大就得夭折了。
聊以自慰:
1. 英雄所见略同
2. 这个轮子很有价值
3. 发明轮子之前,先多看看
4. 多看看,碰到问题就免得发明轮子
有一类问题,代码模板相同,但有少部分地方不同,一般可以写一个复杂的程序,使用不同的选项,完成不同的任务。或者,把公共的部分抽象成一个代码库,然后在不同程序中引用。但是,如果公共的部分很少,并且比较“专用”,或者因为其它原因,比较难以部署。怎么办?
实际上,有另一种完全不同的编程模式来实现:代码生成器。unix世界中最知名的代码生成器莫过于lex和yacc了。但是,不比每个代码生成器都那么复杂,比如这个代码生成器就非常简单,它只是简单地转换行记录:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
#! /bin/sh field_seperator="||" output=b while getopts :F:vo: arg do case $arg in F ) field_seperator=$OPTARG;; v ) ;; o ) output=$OPTARG;; : ) echo "$0: missing arg for -$OPTARG " >&2 exit;; /?) echo "Invalid option -$OPTARG ignored." >&2 exit;; esac done if [ $OPTIND -gt $# ] then # echo OPTIND=$OPTIND argc=$# >&2 echo "no program" >&2 exit fi program=${!#} echo field_seperator=$field_seperator cat > a.cpp <<+TemplateCFile #include <vector> #include <string.h> #include <stdio.h> const char field_seperator[]="||"; void split_row(char* line, std::vector<char*>& F, const char* fs) { char* col = line; F.resize(0); size_t fslen = strlen(fs); if (fslen == 1) { for (;;) { F.push_back(col); col = strchr(col, fs[0]); if (col) { col[0] = '/0'; col += 1; } else break; } } else { for (;;) { F.push_back(col); col = strstr(col, fs); if (col) { col[0] = '/0'; col += fslen; } else break; } } } int main(int argc, char* argv[]) { size_t len1 = 0; ssize_t len2; char* line = NULL; std::vector<char*> F; while ((len2 = getline(&line, &len1, stdin)) != -1) { split_row(line, F, field_seperator); int NF = F.size(); //--- begin user program +TemplateCFile echo $program >> a.cpp cat >> a.cpp <<+TemplateCFile //--- end user program ; // avoid user program missing ; printf("/n"); } if (line) free(line); if (ferror(stdin)) { perror("ferror(stdin)"); return 1; } return 0; } +TemplateCFile sed -i 's//(field_seperator/[/]=/).*";//1"'$field_seperator'";/g' a.cpp gcc -O2 a.cpp -lstdc++ -o $output exit $? |
可以象awk一样写程序:
1 2 3 |
# 相当于 awk -F, '{printf("%s/t%s/n", $1, $5)}' # 使用 ',' 做列分隔符,输出第 1 和第 5 个字段,生成二进制可执行程序 myprog ./gencode.sh -F , -o myprog 'printf("%s/t%s/n", F[0], F[4])' |
1 2 3 |
# 相当于 awk -F, '{printf("%s/t%s/n", $1, $5)}' # 使用 ',' 做列分隔符,输出第 1 和第 5 个字段,生成二进制可执行程序 myprog ./gencode.sh -F , -o myprog 'printf("%s/t%s/n", F[0], F[4])' |
我当初写这个生成器的原因是发现非常简单的 awk 程序也比 C 慢 40 倍,以为这是本质上的性能差距,后来才发现不是。
对这个简单的程序,使用awk更方便更安全,也不比C慢,但是一旦碰到其它类似问题而 awk 解决不了,这种模式就可以派上用场了。
一直在想:如何在 Hadoop.MapReduce 中,插入一个 C 写的 HashFunction,既要高效,又要接口简洁。通过命令行实现调用显然是不行的。刚刚终于想出了:使用管道!
一个非常简单的程序,从stdin读入,写到stdout。多简单!至于效率,管道嘛,本质上就是异步的,自然是buffered&asynchronous 模式。
hash 程序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
#include <stdio.h> int hash(const char* key) { int h = 234234; for (; *key; ++key) h = h << 3 ^ *key; return h; } int main(int argc, char*[] argv) { char buf[256]; while (fgets(buf, sizeof(buf), stdin) != EOF) { printf("%d/n", hash(buf)); } return 0; } |
框架可以一边不断往管道写key,一边从中读取结果,这两个工作完全可以是异步的。对hash程序来说,如果stdin/stdout是全缓冲的,就几乎没有io的开销,因为几百几千次 fgets/printf 才会导致一次系统调用。
对frame程序也是一样的。
在 hadoop.streaming 中,hash 函数目前还必须由 java 类指定,如果使用这种方式,那就更 unix 了。
整体感觉:ACE太庞大,asio 太赶时髦。
ACE太过庞大,使得你即便是只使用它的一小部分,也不得不引用它的全部。而且框架一大堆,模式一个加一个,很多编程习惯也要改变。学习曲线太陡,也难以将它作为一个模块集成自己的应用。
asio呢,有个牛大大说它是现时代的ACE,我觉得比较中肯。用bind做回调也并不比虚函数好,看上去灵活了,代价却更高了。我说的不光是运行时的内存和时间代价,更重要的编译时间难以忍受。
apr大约只是一个平台无关的api封装,相对来说比较轻量级。
libevent就更轻量级了,轻量级到无法把它当成一个平台无关的socket,还要写很多平台相关的代码。
相比而下,我觉得apr还好点,其实我需要的只是一个机制,而不是一个完整的策略,这是unix的哲学。如果仅就异步通讯来说,我觉得linux.epoll是最好的,简单、直接、有效,如果在每个平台上都有epoll可用,所有其它封装在我看来都是多余的了。
proactor真的就比reactor高效吗?它更多的是一种策略,据说Windows的操作系统级IOCP是用线程池实现的,它能高效到哪里去?
在现实世界中,基本是是按着这样的顺序演化:process–>thread–>coroutine/fiber
其实是一个context切换开销从大到小的演化,process切换开销最大,需要切换地址空间,所有的CPU状态,所有其他资源
thread切换只需要切换CPU状态,当然是大部分的CPU状态,而coroutine切换,只需要切换很少的CPU状态,而且全部都在用户地址空间运行,不需要到内核空间。
当然,切换coroutine的开销还是比一次函数调用大很多,其实函数调用也是一个cpu状态的切换,不过这个状态要少得多,在x86 windows 上,甚至不必保存所有的寄存器状态(EAX/ECX/EDX在函数调用之间就不用保证,EAX保存返回值),有些调用甚至通过寄存器传递参数……比起coroutine,太微不足道了。
coroutine其实也可以看成是一个保留了以前调用状态(另一个堆栈帧)的函数调用,在寄存器很多的系统上(如Itanium),切换寄存器状态的开销还是比较大的,如果哪一天大家又开始嫌coroutine也太慢,那怎么办?
在异步通讯中,一般使用一个线程来select/poll/epoll,收到信号后,解码消息头,或者整个消息,然后将相应的fd交给其他线程处理。这看上去的确是个很好的办法,但是…… 继续阅读