如何设计一个内存分配器?

通常工程里不推荐自己写内存分配器,因为你费力写一个出来99%可能性没有内置的好,且内存出bug难调试
不过看书之余,你也可以动手自己试试,当个玩具写写玩玩:

1. 实现教科书上的内存分配器:

做一个链表指向空闲内存,分配就是取出一块来,改写链表,返回,释放就是放回到链表里面,并做好归并。注意做好标记和保护,避免二次释放,还可以花点力气在如何查找最适合大小的内存快的搜索上,减少内存碎片,有空你了还可以把链表换成伙伴算法,写着玩嘛。

2. 实现固定内存分配器:

即实现一个 FreeList,每个 FreeList 用于分配固定大小的内存块,比如用于分配 32字节对象的固定内存分配器,之类的。每个固定内存分配器里面有两个链表,OpenList 用于存储未分配的空闲对象,CloseList用于存储已分配的内存对象,那么所谓的分配就是从 OpenList 中取出一个对象放到 CloseList 里并且返回给用户,释放又是从 CloseList 移回到 OpenList。分配时如果不够,那么就需要增长 OpenList:申请一个大一点的内存块,切割成比如 64 个相同大小的对象添加到 OpenList中。这个固定内存分配器回收的时候,统一把先前向系统申请的内存块全部还给系统。

3. 实现 FreeList 池:

在你实现了 FreeList的基础上,按照不同对象大小(8字节,16字节,32,64,128,256,512,1K。。。64K),构造十多个固定内存分配器,分配内存时根据内存大小查表,决定到底由哪个分配器负责,分配后要在头部的 header 处(ptr[-sizeof(char*)]处)写上 cookie,表示又哪个分配器分配的,这样释放时候你才能正确归还。如果大于64K,则直接用系统的 malloc作为分配,如此以浪费内存为代价你得到了一个分配时间近似O(1)的内存分配器,差不多实现了一个 memcached 的 slab 内存管理器了,但是先别得意。此 slab 非彼 slab(sunos/solaris/linux kernel 的 slab)。这说白了还是一个弱智的 freelist 无法归还内存给操作系统,某个 FreeList 如果高峰期占用了大量内存即使后面不用,也无法支援到其他内存不够的 FreeList,所以我们做的这个和 memcached 类似的分配器其实是比较残缺的,你还需要往下继续优化。

Continue reading

Loading

Posted in 编程技术 | Tagged , | 3 Comments

FlashDevelop 好用

好几年没碰过 Flash 了,最近需要给 ActionScript 导出一些 C 接口,又抽空捡起来。项目大了以后 Flash Builder 卡的要死,经常是一个构建你就可以休息了,按一下 “.“ 它就开始搜索补全提示,你的符号多了以后,有时候 Flash Builder 近乎假死了。实在难以忍受,咨询了一些正在做页游的朋友,是否还在用 Flash Builder 。得到答案是:早就投奔 Flash Develop 了。

试了一下 FlashDevelop 果然腰也不酸了,腿也不疼了,十分流畅,界面类似 Visual Studio,同时还是免费的,可以彻底和笨重的 Flash Builder 说再见了。再次感叹 AS3 写起来真爽之余,记录一下安装配置过程:

image

Continue reading

Loading

Posted in 随笔 | Tagged | 2 Comments

网络程序计时器通常用啥实现?

通常来讲,就是利用 select 的空余时间,来进行时钟检查,不管是 select / poll / epoll/ kevent,以下统称 select,它有一个等待时间作为参数,即没有事件时,最多 wait 多少时间,我们把这个作为网络库的基准频率,比如 10MS,或者 20MS, 25MS, 50MS,都是常用的几个值。

就是说网络库调用 select 等待事件时如果没有事件,那么最长等待 10MS 就返回了,这时再处理完所有网络事件后,就可以来处理时钟数据了。事件处理函数就是这样:

def update_events(milisec = 10):
    result = selector.select(milisec)
    for fd, event in result:
        do something with socket event
    current = time.time()
    update_timer(current)

while 1:
    WAIT_MILLISEC = 10
    update_events(WAIT_MILLISEC)

关键就是这个两次 select 中间 update_timer 的任务:集合中检查需要唤醒的时钟,并且调用它们的回调函数,来驱动整个服务器的时钟运行,以最简单的扫描法为例:

def update_timer (current):
    for timer in available_timers:
         while current >= timer.expires:
             timer.callback(current)
             timer.expires += timer.period

available_timers 记录着当前可用的所有 timer 的集合,expires 是他们需要被触发的时间,如果当前时间大于等于这个 expires,认为该 timer 需要被触发到。注意 timer.expires 更新的时候是 += 周期,而不是 = current + 周期,后者会导致误差积累,长时间运行后偏差越来越大。同时这里需要 while,因为可能跨越两个以上周期,当然只运行一次的 timer 就不需要了,这里只是简化下。

比如 libevent 里面的主循环 event_base_loop 每次 select 完毕后就调用一次 timeout_process。

这就是 Timer 调度的基本原理。

可能你会发现每次 select 结束都要扫描整个 available_timers 集合,是一个非常费时间的事情,那么首先想到的就是优先队列了:将 Timer 节点按照 expires 的先后顺序,将最快要发生的超时节点放在前面,每次检测队列头就可以判断是否超时了。

Continue reading

Loading

Posted in 编程技术, 网络编程 | Tagged | Leave a comment

如何提高编程的手速

可以使用经典的 TT 来测试你的打字速度,注意是包含数字和符号的文章(Menu->Test->All key)

image
软件很简单,按照箭头指着的位置,快速输入上面的单词即可,输入完会有评分的。

TT是比较好的打字练习程序,直到今天,公司内都用作给新人练习打字速度用。不当能测试,还有比较详细的课程,教你从纯单词打起,逐步到数字,标点符号等。

我做过一个 DOSBOX版本的 TT (tt.exe 是 DOS下的程序),双击 TT.BAT 即可在 Win7/8 启动
http://www.skywind.me/mw/images/e/eb/TT-Dosbox.7z

我当时用 TT 测试 All Key 的时候,已经写过好多年程序了,自己觉得自己打字不慢,英文可以流利盲打,数字和符号需要看一下,结果 TT 测试下来,打字速度只有 31 WPM ,属于垫底的角色,丢死人了。

Continue reading

Loading

Posted in 随笔 | 4 Comments

从大公司离职去小公司当 CTO 是一种怎样的体验?

老板非技术或者非产品出生的,从来没参与过项目开发的,对技术工作想想太过简单化的,去了也白去。这样的老板,对 CTO 的定位就是 “魔术师”,好像招聘到一个厉害的魔术师过来,再宏伟的需求,只要 CTO 够牛,最多几天时间,他都能把想要的东西给 “变” 出来。这就是不懂研发的老板们对 CTO 的真实期望,你以其花一两年时间慢慢 “教” 会他研发的艰辛,还不如考虑一下换个地方。

再者前期东西做出来前你很重要,后期东西出来后靠运营的时候你就比较尴尬了。老板无法正确评估你的价值,东西出来后,技术做的好就是不出问题,老板看不到,看得到的时候就是出问题的时候了,好像每次赢得利润都是商务和运营的努力。

你需要争取资源和开发时间进行优化或者开发一些非功能性,界面上体现不出来的功能,你都会发现异常难以向老板说明他的重要性。

每次发奖金和分红的时候,老板都会心理暗自嘀咕,“我靠,技术那么高的工资,原来一直跟着我干的那帮商务兄弟们才拿那么点,利润又是他们创造的,好可怜呀。技术成天没开发啥新功能,老的也做不好,上周才出一次事故。。。。”

记住,这样的老板,对 CTO 的期望基本上就是停留在 “变魔术” 三个字上,出外创业,除了项目靠不靠谱,还得看看创始人的基因及期望。

等到哪天你离职时,期权股份一回收,你这两年就白忙了。

以上为身边大数据统计出来的结论,信则有,不信则无。

Loading

Posted in 大浪淘沙 | Tagged | Leave a comment

EPoll 和高性能没什么关系

现在很多人一提高性能后端开发,就总会想起 EPoll 来。其实一个成熟的高性能服务器,epoll相关的代码,不到万分之一。

而往往入门服务端的人,都天真的人为:高性能服务端开发 == EPOLL,真好笑,之所以会出现 epoll这种被捧上天的垃圾,明明就是 posix 或者最早版本的 unix/bsd/systemv 的设计考虑不完善。

按今天的眼光反思 posix 和 unix/bsd/systemv 当年的设计,epoll 这种补丁就不应该实现。

异步 reactor 框架应该就只有一个简单而统一的 selector 就足够了,所有系统都相同,提供:

  • register: 注册
  • unregister:删除
  • set:设置
  • wait:等待事件
  • read:读取事件
  • wake:将等待中的 wait 无条件唤醒

别以为这些 poll / epoll / kevent / pollset / devpoll / select / rtsig
是些什么 “高性能服务器” 的 “关键技术”,它们只是一个 API,而且是对原有系统 API设计不完善打的补丁,各个内核实现了一套自己的补丁方式,它们的存在,见证了服务端技术碎片化的遗憾结果。

之所以会有这些乱七八糟的东西,就是早期的 posix / unix/ bsd /systemv 设计不周全,或者不作为留下的恶果。并非什么 “关键技术”。

不用提 windows 的 iocp了,proactor 会来强奸你代码结构,遭到大家唾弃是有原因的。不像 reactor那样优雅,所以 java nio 选择 reactor 是正确的。即便在 reactor 中,epoll 也是一个失败的例子,调用最频繁的 epoll_ctl 的系统占用估计大家都感受过吧,这方面 epoll 真该象 kevent / pollset 学习一下。

Loading

Posted in 网络编程 | Tagged | Leave a comment

学习好的都做了打工仔?

学习好的都做了打工仔?学习不好的都做了创业狼?

不读书的人往往心态比较偏执,经常一口咬定读书无用。而读书好的人往往兼容并包,承认三百六十行,行行都能出状元。

有一次在火车上碰到一个厨师,满口大放厥词,说自己在五星级酒店炒菜,一个月月薪5000,比很多大学生都高,然后反复说来说去,读大学有什么用?

读书好的人,内心往往更加宽广,不会瞧不起不如自己的职业,也不会在各种老板们面前卑躬屈膝。

而题主这样不读书的人,正和那个厨师一样心态狭隘,做不到就说没用,见到别人比自己差就陷入极度的自大,和厨师一样口吐狂言;见到收入超过自己的老板们,又陷入极度的自卑,和题主一样充满奴性的跪舔。

象题主和饭店厨师这样书读不好心态又偏激的人,还有其他一些特征:好面子、不善于控制自己情绪,冲动起来比别人加倍冲动,低落起来比别人加倍低落、同家庭关系不好、同上司关系紧张,内心脆弱,经常需要在各种鸡汤里找到自信。。。。等等。。。。

Loading

Posted in 随笔 | Tagged | Leave a comment

什么时候用C而不用C++?

知乎问题《什么时候用C而不用C++?》:

前两天不是有一个问题是“什么时候用C++而不用C”,我一直觉得问错了,难道不是“能用C++就不用C”么?那么当然就要讨论什么时候用C而不用C++啦。

一直以来都严格遵循OO的原则来进行开发(用的工具是C#和Qt),直到最近,开始接手某同事的代码,整个项目20多个小工程(代码量并不多),除了界面部分用了MFC这种不伦不类的OO以外,所有的代码都是C写的。但是模块化做的非常好。后来跟他讨论为何不用C++,他说其实没有什么特别的,就是习惯和爱好而已,后又补充:

如果不用多态的话,其实不管怎么写,不管用那种语言写,都算不上真正的OO

忽然觉得很有道理……

其实这是一个好问题,

题主开始欣赏到纯 C代码所带来的 “美感” 了,即简单性和可拆分性。代码是自底向上构造,一个模块只做好一个模块的事情,任意拆分组合。对于有参考的 OOP系统建模,自顶向下的构造代码抽象方法是有效率的,是方便的,对于新领域,没有任何参考时,刻意抽象会带来额外负担,并进一步增加系统耦合性,设计调整,往往需要大面积修改代码。

有兴趣你可以读读《Unix编程艺术》,OOP的思维模式,是大一统的;C的思维模式,是分离的。前者方便但容易造成高耦合,后者灵活但开发开发太累。用 C开发,应该刻意强调 “简单” 和 “可拆分”。一个个象搭积木一样的把基础系统搭建出来,哪个模块出问题,局部替换即可。

自底向上的开发模式,并不是从不站在大局考虑问题,而是从某个子系统具体实现开始,从局部迭代,逐步反思全局设计,刻意保持低偶合,一个模块一个模块的来,再逐步尝试组合。

自底向上强调先有实践,再总结理论,理论反过来指导实践,又从实践中迭代修正理论。这和人类认识世界的顺序是一样的,先捕猎筑巢,反思自然是怎么回事,又发现可以生火,又思考自然到底怎么回事情。

它的反面,是指大一统设计,你一开始用 UML画出整套系统的类结构,然后再开工设计。这种思维习惯,如果是参考已有系统做一个类似的设计,问题不大,全新设计的话,他总有一个前提,就是 “你能完整认识整个大自然”,就像人类一开始就要认识捕猎和筑巢还有取火一样。否则每次对世界有了新认识,OOP的自顶向下设计方法都能给你带来巨大的负担。

所以有些人才会说:OOP设计习惯会依赖一系列设计灵巧的 BaseObject,然而过段时间后再来看你的项目,当其中某个基础抽象类出现问题是,往往面临大范围的代码调整。这其实就是他们使用自顶向下思维方法,在逐步进入新世界时候,所带来的困惑。

当然也有人批判这种强调简单性和可拆分性的 Unix思维。认为世界不是总能保持简单和可拆分的,他们之间是有各种千丝万缕联系的,你一味的保持简单性和可拆分性,你会让别人很累。这里给你个药方,底层系统,基础组建,尽量用 C的方法,很好的设计成模块,随着你编程的积累,这些模块象积木一样越来越多,而彼此都无太大关系,甚至不少 .c文件都能独立运行,并没有一个一统天下的 common.h让大家去 include,接口其他语言也方便。

然后在你做到具体应用时根据不同的需求,用C++或者其他语言,将他们象胶水一样粘合起来。这时候,再把你的 common.h,写到你的 C++或者其他语言里面去。当然,作为胶水的语言不一定非要是 C++了,也可以是其他语言。
————-
PS: 这里主要在探讨 OOP存在的问题,并没有讨论嵌入式这种资源限制的情况,以及操作系统和底层等需要精确控制硬件和内存的情况,更没有讨论 C++在语言设计层面的事情。

————-

转部分答疑:(点击more展开)

Continue reading

Loading

Posted in 编译原理, 随笔 | Tagged | 1 Comment