内存池之二:定长内存池

内存池是一种用来分配内存的池技术,重点在“池”,即内存的重用上。
重点不在“池”上的内存分配技术当然也是有的,比如stl的内存分配器(SGI STL使用了内存池,而有很多其他版本的STL则没有),重点在“分配器”的概念上。
内存的重用,能够带来不少好处。最为直观的好处是,提高内存分配/释放的速度;不大明显的好处,则是避免程序产生内存碎片(即空闲却又无法利用的内存)。
另外,由于内存池托管了内存分配,其实它属于内存分配器的一种,因此内存分配器的好处它也自然可以享受到。比如在内存分配的时候增加一些标记,可以使之支持内存泄露检测之类。
内存池根据各自的适用场景,又分为很多种:比如单线程/多线程的分类;又比如定长/变长的分类。
想要实现一个通用(即可以在各种场景下使用),又高效的内存池,不是一件容易的事情;学习、对比、然后使用第三方的内存池,也需要一定的时间成本。所以很多时候,在自己的代码里,又或实际的项目中,若非有确凿的证据表明内存管理方面存在不可忽视的瓶颈,否则大家使用的还是new/delete,或者malloc/free。
下面我们一起来实现一个简单的定长内存池,并尝试对它进行一些拓展。 继续阅读

C++的杂七杂八:constexpr与编译期计算

1. 编译期计算

我们先来看一段求阶乘(factorial)的算法:

很明显,这是一段运行期算法。程序运行的时候,传递一个值,它可以是一个变量,也可以是一个常量:

如果程序仅仅像这样传递常量,我们可能会希望能够让它完全在编译的时候就把结果计算出来,那么代码改成这样或许是个不错的选择: 继续阅读

Linux内核的编译和替换方法总结

以我目前的环境为例,我的Ubuntu系统的内核版本是3.19.0,我想使用新版本的内核4.5.0来替代它,步骤如下(以下过程全部在root权限下操作):

1. 安装必备软件编译工具

注意:
1)libncurses5-dev是为之后配置内核能运行make menuconfig程序做准备;Build-essential是编译工具,kernel-package是编译内核工具;
2)如果系统显示无法查找到这三个文件,输入apt-get update更新数据源。

2. 下载内核

继续阅读

C++的杂七杂八:萃取函数(可调用体)的各种属性

在C++中,存在“可调用对象(callable objects)”这么一个概念。这里我直接摘录C++11标准《ISO/IEC 14882:2011》,§ 20.8.1 Definitions:

A callable type is a function object type (20.8) or a pointer to member.
A callable object is an object of a callable type.

同时,再根据 § 20.8 Function objects:

A function object type is an object type (3.9) that can be the type of the postfix-expression in a function call (5.2.2, 13.3.1.1). A function object is an object of a function object type. In the places where one would expect to pass a pointer to a function to an algorithmic template (Clause 25), the interface is specified to accept a function object. This not only makes algorithmic templates work with pointers to functions, but also enables them to work with arbitrary function objects.

我们可以总结出callable objects的基本定义: 继续阅读

C++的杂七杂八:如何实现一个简单的bind

这篇文的草稿我是在2014年5月11号开始打的,可是拖拖拉拉直到现在才真正动笔写,自己对自己也是醉了。。
之所以写bind而不是什么其他的东西,是因为bind在各种C++的utility里面可以说是最能体现出“利用语言本身来拓展语言功能”这一特征的了。

在C++98/03的时代,人们为了让C++具有把函数和参数打包成闭包(closure)这一能力而发明了bind,从而使C++不仅可以存储算法执行的结果,还可以打包算法和算法的传入参数,存储算法执行的动作,直到调用这个闭包的时候才释放出参数并参与算法计算。有了这个东西之后,再配合可以存储任何可调用体的function对象,不少面向对象里麻烦的调用关系可以被简化到不像话的地步(具体可参看此文:以boost::function和boost:bind取代虚函数)。

在C++98/03下实现一个好用的bind是痛苦的。看看Boost.Bind,还有我自己,为了实现一个完善的bind折腾遍了C++的奇巧淫技……这是很划不来的,平时学习或兴趣玩一玩还可以,真在项目工程里这样做了,如何维护会是一个很头痛的事情。

在C++11里事情变得很不一样了。首先stl里就已经给我们提供了好用的std::bind,然后再就是语言本身的进化,让“写一个bind”之类的事情变得无比简单。 继续阅读

一款C++静态分析工具 —— CppDepend

今年6月份的时候,CppDepend的一位技术社区经理(technical community manager)突然发邮件联系我,赠送了我一份license,并邀请我体验下他们的C++静态分析工具,也就是CppDepend。

在这款软件的官方网站上,我们可以看到使用CppDepend可以帮助我们做些什么:

  • 1. 提高代码质量
  • 2. 可以使用CQLinq来自动化Review代码
  • 3. 帮我们做代码重构
  • 废话不多说,我直接使用它对我自己的项目Capo(https://github.com/mutouyun/capo)做一次简单的静态分析,来直观的感受下CppDepend的功能。
    继续阅读

    内存池之一:基本概念

    1. 内存池是什么

    内存池(Memory Pool),是内存分配器(Memory Allocation)的一种表现形式。它以预存储的方式预先分配一大块内存(相对于每次请求的内存大小来说),使得绝大部分的内存请求只需要在已分配的大块内存上划分出一小块来即可。

    与malloc/free相比的优点:

  • A.分配/释放内存的速度

  • 因为内存池绝大部分情况下是用户态、无锁、O(1)或O(logN)的时间复杂度,所以速度会比malloc/free要快很多。
    尤其是在多线程环境下,系统级的malloc需要加锁,陷入内核态之后会非常的慢;而一个设计良好的多线程内存池可以让分配速度提高一个数量级。

  • B.避免内存碎片

  • 频繁且不规律的malloc/free,尤其是大量小对象的时候,可能会导致内存释放后,空闲的内存块被已分配的内存块分割成无法合并的多块小“碎块”。此时突然申请一块大内存,虽然总的空闲内存大小是足够的,但我们却无法利用它们来完成内存分配。
    正确的使用内存池,可以在一定程度上避免内存碎片的产生。

  • C.增加内存的利用率

  • 由于malloc在分配内存时,需要增加一些必要的簿记信息,也就是记录内存块信息的头部结构,因此每次内存分配都会导致一定程度的浪费。
    而内存池不同,可以做到按需分配。使用得当的话内存浪费会很小。

    当然了,使用内存池的缺点是会导致一定程度上的编程复杂度增加。不同方法实现的内存池有各自在使用上需要注意的地方,随随便便的用可能会引起意料之外的问题。 继续阅读

    C++的杂七杂八:关于内存对齐的那些事

    1. 内存对齐(Data Structure Alignment)是什么

    内存对齐,或者说字节对齐,是一个数据类型所能存放的内存地址的属性(Alignment is a property of a memory address)。
    这个属性是一个无符号整数,并且这个整数必须是2的N次方(1、2、4、8、……、1024、……)。
    当我们说,一个数据类型的内存对齐为8时,意思就是指这个数据类型所定义出来的所有变量,其内存地址都是8的倍数。

    当一个基本数据类型(fundamental types)的对齐属性,和这个数据类型的大小相等时,这种对齐方式称作自然对齐(naturally aligned)。
    比如,一个4字节大小的int型数据,默认情况下它的字节对齐也是4。

    2. 为什么我们需要内存对齐

    继续阅读

    为什么直接杀死线程是不好的

    我们知道,windows里有个API叫TerminateThread,它可以干掉任何正在欢快小跑的线程。对应的,liunx里则是pthread_cancel(不是pthread_kill,这玩意本质是向线程发信号,而不是杀死线程)加上PTHREAD_CANCEL_ASYNCHRONOUS。

    但是我们同时也看到,不论是哪种方法,在它们的手册里都不推荐我们使用它们。

    比如微软的msdn中对TerminateThread的描述:

    TerminateThread is a dangerous function that should only be used in the most extreme cases. You should call TerminateThread only if you know exactly what the target thread is doing, and you control all of the code that the target thread could possibly be running at the time of the termination.

    再比如Pthread API Reference中的一段话:

    It is recommended that your application not use asynchronous thread cancelation via the PTHREAD_CANCEL_ASYNCHRONOUS option of pthread_setcanceltype().

    特别的,在C++11的标准库中干脆去掉了Thread的Cancellation;在某些语言中(比如Python),我们甚至无法由外部强制终止某个线程。

    那么为什么直接杀死线程是不好的呢? 继续阅读