Dec 15

一直以来我都表达 python 是个糟糕语言的观点,但是没有深入的解释。

计算机一直都是“工具”,意味着我们是拿它干活的,也就是所谓的提高生产力。

 

指挥计算机干活的重要工具就是编程语言。计算机并不是训练来干活的,是编程来干活的。编程语言的效率有2个指标:编写干活指南的效率和机器人执行的效率。

 

在人力成本低于硬件的时候,人追捧的是执行效率。

人力成本越来越高的时候,人开始追求编程的效率。

人自然是希望一个语言能两头兼顾。可惜的是 python非但没有带来执行效率(这是python不追求的,所以姑且不算缺点),连它拼命牺牲执行效率希望换来的开发效率事实上也一点没有。

 

执行效率:首先,执行效率和语言本身高级不高级是没有任何关系的。执行效率的高低只关系到冗余操作的多寡。这也是“优化”的基础,去除冗余操作。

冗余操作的多寡通常有3个因素影响到:1 编译器的效率  2 程序员的水平 3 语言本身的累赘

但是衡量一个语言本身效率的,事实上应该是最后一个 "语言本身的累赘",这个才是编译器永远无法改进的,程序员水平再高也无能为力的。

不幸的是,python是一个本身的累赘非常多的语言。而累赘最少的语言,应该算是C++语言了。所有C++用到的功能,没有一个是可以在别的语言用更低的代价实现的。当然,有的语言压根就没有C++提供的功能,必须自己模拟。模拟的代价和C++提供的是一样的,水平不够的人来模拟只能获得更烂的结果。

当然 ,python是个高级语言,语法糖多点,性能烂就接受一下吧!毕竟开发效率高呢(?)

接下来我们说编程的效率。编程效率主要是受5个因素影响:语法是否自然,语义是否凝炼,文档或者或教程多不多,库是不是丰富,开发环境好不好。不用说python的文档还是很多的,但是显然没有c++多。各种粗制滥造的c++教程铺天盖地,算了,这是c++的坏处。误人子弟的教程太多。库当然是C++最丰富了。python还面临着 python3 和 python2 的分裂。

 

开发环境和库一直是C++的优势。不用说 Visual C++这种重量级公司出的IDE ,还有 kdevelop , eclipse CDT 这些免费开源的IDE。自动完成和代码提示让你写代码的时候非常轻松。相比之下 , python 就没有好的IDE了。

当然,接下来是程序员最关心的,语法是否自然,语义是否凝炼。python 的语义自然是很多的,一条语句能相当于写几千行 C 代码。这也是人常说的,语法糖多。可惜的是,C++一样有高级语法糖,而且代价很低!比python低太多了。在这一点上,python没有优势,只有劣质。python的语法糖是以牺牲性能换来的,而C++在不损失性能的同时提供了语法糖。

 

至于语法自然不,仁者见仁智者见智了。对于大多数C学过来的人来说, 自然是C++的语法简单。当然,前提是不使用模板这种高级货。真的用模板的话,模板是属于用起来简单,写起来难的语法糖。我们自然可以选择把困难留给 boost,快乐留给自己。所以这点上 C++没有输。何况python还不支持模板。

 

那么开发效率到底是 C++高还是 python呢?

差不多!

那执行效率呢? 

C++和python不是一个档次的,没法比。

 

好了,单从这点已经 python 完败。不过我想说的还不是这个。

 

如果真的有语言像python那样慢,我觉得对得起它的性能的,就必须拿出像样的功能,这个功能包括

 

语言级的并行能力,语言级的多进程能力 (等等,这不就是shell么!),语言级的SIMD能力(语言级的矩阵运算支持)

语言级的复杂数学公式计算能力  ,内置的标准各种算法(STL笑而不语,不过我要的还不止STL),描述性语义(而非指令性语义)

 

我要求的这些,是高级语言需要具备的,而 python 统统没有,连 shell 都能占上一些!

 

 

 

 

Nov 1

传送门: http://microcai.gsalex.net/2012/10/内存管理随笔

 

思考一:

任何一个程序,只要不是 helloworld 那样的简单程序,必然会用到内存管理。内存管理是写程序不可避免的过程。C程序员最大的恶魔就是野指针和内存泄漏。这是每个C程序员的噩梦。C++继承了 C的缺陷,基本上半斤八两。而且C++还可以自由重载new操作符,给内存管理更加重了复杂性。
我常常在想,写BASH脚本的时候,我们有管理过内存么?即便是java那样的语言,内存管理也是后台进行的,并不是可避免的,俗称GC。可是写脚本的时候,真的完全没用内存管理方面的困扰。

到底是哪里出了问题呢?到底为何脚本就不用管理内存?

思考二:

能不能完全不依靠malloc/free new/delete 编写出一个 c/c++程序呢?

结果是显然的,能!

但是只有对 hello world 那样的程序才有用。最近的一项小随笔项目证实了我的想法 https://github.com/microcai/hm 。在这个程序里,我没用使用任何 malloc/free new/delete 来管理内存。

为何这个程序可以不用内存管理呢? 于是在另一个随笔项目里,我依然使用了内存管理,虽然是智能指针自动管理的,但是毕竟使用了 new 操作   https://github.com/microcai/googleproxy 。我在想,凭啥这个程序就必须使用  new 了?

思考三:

程序,说到底就是一个状态机。在 hm 程序里,我采用的是“过程化”编程,外加同步多进程的IO模型。在 googleproxy 程序里,使用的是单线程异步IO的模型。

hm里,状态机的状态就是进程的状态。一步一步执行下去,状态随之切换。cpu执行到哪一行,哪一行就是当前状态。

googleproxy里,情况有了变化,因为使用了异步,所有的状态变化都是围绕一个中心进行的: boost::io_service::run() 。 在 run() 里,通过回调来通知状态的变化。也就是说,cpu执行到哪个回调,哪个就是当前状态。

区别是什么:

在不同的状态之间,我们有数据要共享! hm 多使用局部变量,不同的状态需要共享数据,通常也处于同一代码层级!可以直接引用需要的数据!

googleproxy 里,不同的状态之间,是不同的 run() 操作的回调,需要共享数据,但是上一个状态的局部变量已经消失!上一个状态的局部变量已经消失!

要跨栈域引用共享数据,唯一的办法就是将数据创建在堆上!

简单的来说,在一个程序里,所有代码的执行路径可以归纳为从 main() 开始的一个调用树。一个函数只能引用本层和上层的局部变量,无法引用子层和兄弟层和兄弟的子层的局部变量。因为这些地方的变量都是不存在的呀!

所以,只能将共享数据创建在堆上。这样才能跨过调用树的生存周期!

这也是唯一需要堆管理的理由。

如果一个变量要进入函数的时候创建在堆上,退出的时候释放,通常的做法其实就是使用栈变量,或者是使用可变长度的stl容器。

如果返回值是对象,也不要使用指针了,直接返回对象吧!别担心临时对象拷贝开销了。

只有当跨栈域共享对象的时候,才考虑使用堆吧!
记住:即便如此,也不要使用裸指针。裸指针只用来进行直接内存访问(底层编程的时候),千万别用来引用对象。使用智能指针吧!放弃对象的拷贝开销的担心 吧! 那几百个周期的拷贝操作比 new/delete 的内存管理代码的开销相比,还是低太多里。何况有了 c++11 的 Move 语义,很多时候已经没用拷贝开销里。

 

放心大胆的脚本化C++程序吧! 实在需要跨栈共享数据的时候,使用 shared_ptr 引用吧!

Sep 17

闲来上网,看到了 http://coolshell.cn/articles/8138.html 在反对算法面试。

他说的对也不对。他认为算法很重要,但是不应该出纯算法题目。不应该出算法题目,我赞成,但是如果说算法很重要,我就要哈哈大笑了。

算法什么也不是。

软件是一种艺术。如果艺术能用算法描述那他也就不能称为艺术了。没错,有时候确实需要算法。排序么?恩,需要一些排序算法。

 

但是就这样理解为软件为算法的推切就大错特错了。这就和把油画理解为油墨的堆砌一样可笑。

艺术需要东西去表现,对于绘画,是墨水和纸张。对于程序,是代码和机器。

算法不过是将那个驱动机器的代码进行了抽象,使得不会写程序的人也可以来凑热闹而已。

 

真正的程序,一定是一种思想。这种思想借助具体的硬件去表现。用具体的代码去驱动硬件。

但是程序本身从来也不是算法的堆砌。

 

程序是人类思想的机器描述。背下康熙字典一样写不出红楼梦。背下牛津词典也写不出莎士比亚戏剧。会各种算法也写不出好的程序。

 

唯有思想才能助你写出完美的程序。

 

UNIX就是一种思想。就算UNIX的代码最终被AT&T扼杀,UNIX的思想仍然传播下去,在Linux的土壤中再次发芽。

 

算法是轮子,是我们快速表达思想的工具。最好的优化的是思想,其次是算法,最烂的是在指令层面进行优化,当然,比没有强。

 

寄希望于算法的人,是没有思想的代码生成工具而已。

 

 

Jul 16

内核里的 VT 代码非常 urgly ,buggy ,充斥着许多 hack .

更重要的是不能有unicode font

要是能在 userspace 实现 vt 就能有 unicode font 了。

 

Jul 13

内核方面的缺点:

  1. 缺乏真正的内核级 AIO 支持。

真正的 AIO 必须满足下面的几个要求

  1. 异步操作和同步操作一样都是可以利用缓存的
  2. 没有在内部使用辅助线程 , io 操作应该直接提交给 io scheduler。
  3. 异步操作不会在内部隐式转化成同步操作。比如需要的内容已经在内存的时候, 一个异步 read 会变成同步 read . 尽管read没有阻塞,但是如果是非常大的 buffer 执行拷贝过程可能超过预期,导致不必要的延迟。
  4. 实现上不能对发起的io个数有限制。可以用 ulimit 限制进程和单个线程能同时发起的io操作个数。
  5. 异步操作要能用在所有的 文件描述符 上。也就是说,所有的 io 操作都可以是异步的。
  6. 如果不使用缓存,异步操作需要利用零拷贝技术。直接拷贝到用户提供的 buffer 上而不先拷贝到内核页面。如果用户 buffer 会发生 pagefaut 则应该立即返回错误而不是阻塞。用户提供的 buffer 必须使用 MAP_POPULATED 参数调用 mmap 分配。
  7. 同步io操作在内核里转化为异步io,或者是在 libc 级别(更好)。

SystemV IPC 糟糕的设计:

  1. 进程退出后消息列队、共享内存、信号量无法被自动清除。需要在程序里显式删除。 出错的程序可能因此耗尽系统资源
  2. 全局命名空间, key_t。