现在的位置: 首页 > 综合 > 正文

一个Python程序引发的讨论[续]

2013年10月08日 ⁄ 综合 ⁄ 共 7133字 ⁄ 字号 评论关闭

几点说明:
1 作者不懂Python,本文也不讨论它。之所以用这个题目,缘于一篇旧文一个Python程序引发的讨论。如果不了解来龙去脉,还请预先读之,前篇已有的内容,本篇不会再重复。
2 本文主要讨论一些C++的优化技巧,提高程序效率是主要目的。本文仅讨论优化要点,不会详细分析所有代码。
3 部分通用算法可以在很多开源里找到,因此本文不含通用代码,也无法编译。通用算法可以有以下参考:二叉树,参考stlport的rb_tree;快速内存非配,参考本文前篇。

第一部分 算法简介
1 用文件映射读取数据。实际上使用小缓存方式读入数据,效率也不差。不过文件映射代码简单,就先用着了。
2 使用二叉树进行字符串唯一性判断。在前篇中,最后作者使用hash算法结合二叉树来进行字符串唯一性判定。这一次优化了二叉树算法,hash就不必要了。
3 使用小缓存分批写入数据。

第二部分 二叉树优化
标准库里的rb_tree(map/set)实在太好用了,好得让人忘记了它的底层机制。其实,我们完全可以实现一个类型无关的封装,只包含一些最基本的操作,如:插入节点,删除节点等。推荐研读一下stlport的代码,那里面就有最经典的底层操作封装。

最简单的multimap插入节点算法,应该是下面这个样子:
    rb_node* y = &m_node;
    rb_node* x = m_node.m_parent;
    bool comp = true;
    while (x != 0)
    {
        y = x;
        comp = compare(v, x);
        x = comp ? x->m_left : x->m_right;
    }
    do_insert(y, v, comp);
那个do_insert就可以实现成类型无关的通用算法。我们的优化主要是针对do_insert之前那个小循环。注意,multimap算法不能直接用,实际上,我们需要的是map算法,这个容器不能有重复元素。

优化步骤1:比较子返回值
标准库的容器都要求提供一个比较子,返回true或者false。不过字符串的比较还是返回三个结果比较划算,因此算法可以修改成
    int comp = -1;
    while (x != 0)
    {
        y = x;
        comp = compare_str(v, x);
  if(comp == 0) return false;
        x = comp < 0 ? x->m_left : x->m_right;
    }
这个算法在小循环里判断了是否相等,这样就可以把multimap的插入算法转变成了map的插入算法。multimap和map的插入算法有何区别,还是推荐看stlport。这样的做法一来可以在找到元素后直接返回,二来可以免去循环结束时的节点递减和再度比较等操作。

优化步骤2:按长度分组
内存比较总是要先进行一次长度比较,这不划算。别小看这个长度比较,在这个大循环里,还是有些消耗的。而且给每个字符串保留一个长度也浪费内存的。写前篇的时候也用过strcmp,不过后来感觉还是memcmp快一些。
按照字符串长度分组,每种长度的字符串插入到指定的map里。比较时就可以不管长度了。
实际上,步骤1和步骤2是相辅相成的,单独进行任何一步都意义不大。进行了这两部优化以后,效率基本上与前篇的hash算法相当了。

优化步骤3:数据改为整数
既然变成简单的数据比较了,那么是不是字符串就无所谓了。因此可以把字符串修改成整数数据。这个改动能使整体效率提高30%。

第三部分 效果讨论
写前篇的时候,CPU是AMD2600+。现在的电脑,CPU是赛扬3.06G。
运行前篇的那个hash算法实现,最短时间为546ms。
执行现在的这个算法,最短时间为391ms。

还实现了一个多线程版的,在双核(Core2Duo7500)笔记本上试验了一下,最短时间是220ms。笔记本上单线程执行时间为280ms。多线程带来的好处不是很明显,可能是因为IO的瓶颈。由于效果一般,就不提供多线程版的代码了。

第四部分 代码
这个代码不包含通用算法,如果都贴出来,太多了。因此不要尝试编译它,就是读个思路。

struct rb_item : rb_node
{
    uint_t  m_data[1];
};

struct rb_item_s : rb_node
{
    size_t  m_size;
    uint_t  m_data[1];
};

struct string_mgr
{
    alg::grow_heap* m_heap;
    rb_data m_rb_data[rb_count];

    string_mgr(alg::grow_heap* heap);
    bool insert(uint_t* p, size_t c);
    bool insert(const char* p, size_t c);
};

string_mgr::string_mgr(alg::grow_heap* heap)
{
    m_heap = heap;
    for(size_t i = 0; i < rb_count; ++ i)
    {
        m_rb_data[i].fn_reset();
    }
}

bool string_mgr::insert(uint_t* v, size_t c)
{
    if(c < rb_count)
    {
        if(c == 0) return false;

        rb_data* rb = m_rb_data+c;
        rb_node* y = &rb->m_node;
        rb_node* x = y->m_parent;
        int comp = -1;
        while(x)
        {
            y = x;
            uint_t* d = static_cast<rb_item*>(x)->m_data;
            size_t i = 0;
            while(v[i] == d[i])
            {
                if(++i == c) return false;
            }
            if(v[i] < d[i])
            {
                comp = -1;
                x = x->m_left;
            }
            else
            {
                comp = 1;
                x = x->m_right;
            }
        }

        // allocate item
        rb_item* z  = (rb_item*)m_heap->allocate(sizeof(rb_item)+sizeof(uint_t)*(c-1));
        memcpy(z->m_data, v, sizeof(uint_t)*c);

        // insert to rb_tree
        rb->fn_insert(y, z, comp < 0);
        return true;
    }
    rb_data* rb = m_rb_data;
    rb_node* y = &rb->m_node;
    rb_node* x = y->m_parent;
    int comp = -1;
    while(x)
    {
        y = x;
        size_t xc = static_cast<rb_item_s*>(x)->m_size;
        if(xc == c)
        {
            comp = memcmp(v, static_cast<rb_item_s*>(x)->m_data, sizeof(uint_t)*c);
            if(comp == 0)
            {
                return false;
            }
        }
        else
        {
            comp = c < xc ? -1 : 1;
        }
        x = comp < 0 ? x->m_left : x->m_right;
    }
    // allocate item
    rb_item_s* z = (rb_item_s*)m_heap->allocate(sizeof(rb_item_s)+sizeof(uint_t)*(c-1));
    memcpy(z->m_data, v, sizeof(uint_t)*c);
    z->m_size = c;

    // insert to rb_tree
    rb->fn_insert(y, z, comp < 0);
    return true;
}

bool string_mgr::insert(const char* v, size_t c)
{
    if(c <= rb_count*sizeof(uint_t))
    {
        if(c == 0) return false;
        uint_t s[rb_count];
        size_t i = (c+3) >> 2;
        s[i-1] = 0;
        memcpy(s, v, c);
        return insert(s, i);
    }
    else
    {
        size_t i = (c+3) >> 2;
        uint_t* s = new uint_t[i];
        s[i-1] = 0;
        memcpy(s, v, c);
        bool r = insert(s, i);
        delete [] s;
        return r;
    }
}

// line_reader

struct line_reader
{
    io::fmapping m_file;
    const char* m_pos;
    const char* m_end;
    const char* m_data;
    size_t      m_size;
    size_t      m_hash;

    line_reader(const wstring& path);
    void set(const char* p);
    bool get();
};

line_reader::line_reader(const wstring& path)
{
    m_pos   = 0;
    m_end   = 0;
    m_data  = 0;
    m_size  = 0;

    m_file.create(path.c_str());
    m_pos   = (char*)m_file.m_data;
    m_end   = m_pos+m_file.m_size;
}

void line_reader::set(const char* p)
{
    m_data = m_pos;
    m_size = p-m_pos;
}

bool line_reader::get()
{
    const char* p = m_pos;
    for(; p < m_end; ++ p)
    {
        switch(*p)
        {
        case '/r':
            set(p);
            ++ p;
            if(p < m_end && *p == '/n')
            {
                ++ p;
            }
            m_pos = p;
            return true;
        case '/n':
            set(p);
            ++ p;
            m_pos = p;
            return true;
        }
    }
    if(m_pos == p) return false;

    set(p);
    m_pos   = p;
    return true;
}

// line_writer

struct line_writer
{
    io::fstream m_stream;
    char*   m_pos;
    char*   m_end;
    char*   m_data;
    size_t  m_size;

    line_writer(const wstring& path);
    ~line_writer();
    void add(const char* p, size_t c);
    void flush();
    void allocate();
};

line_writer::line_writer(const wstring& path)
{
    m_pos   = 0;
    m_end   = 0;
    m_data  = 0;
    m_size  = 0;
    m_stream.open(path, io::access_t::write, io::share_t::read, io::open_t::create);
}

line_writer::~line_writer()
{
    flush();
    if(m_data)
    {
        ::free(m_data);
    }
}

void line_writer::add(const char* p, size_t c)
{
    for(;;)
    {
        size_t r = m_end-m_pos;
        if(r >= c+2)
        {
            ::memcpy(m_pos, p, c);
            m_pos += c;
            *m_pos++ = '/r';
            *m_pos++ = '/n';
            return;
        }

        switch(c+2-r)
        {
        case 1:
            ::memcpy(m_pos, p, c);
            m_pos += c;
            *m_pos++ = '/r';
            flush();
            *m_pos++ = '/n';
            return;
        case 2:
            ::memcpy(m_pos, p, c);
            m_pos += c;
            flush();
            *m_pos++ = '/r';
            *m_pos++ = '/n';
            return;
        default:
            ::memcpy(m_pos, p, r);
            m_pos += r;
            flush();
            p += r;
            c -= r;
            break;
        }
    }
}

void line_writer::flush()
{
    if(m_data && m_data < m_pos)
    {
        size_t w;
        m_stream.write(m_data, m_pos-m_data, w);
    }
    allocate();
    m_pos = m_data;
    m_end = m_data+m_size;
}

void line_writer::allocate()
{
    if(m_data) return;

    m_size = 32*1024;
    m_data = (char*)::malloc(m_size);
}

void main()
{
    wstring data_dir = io::fsys::extract_dir(io::fsys::app_dir())+L"//data//";
    wstring path_in  = data_dir+L"email.txt";
    wstring path_out = data_dir+L"email_new.txt";
    for(int i = 0; i < 10; ++ i)
    {
        clock_t tm = clock();
        {
            line_reader reader(path_in);
            line_writer writer(path_out);

            alg::grow_heap heap;
            heap.m_page_size = 64000;
            string_mgr mgr(&heap);

            while(reader.get())
            {
                if(mgr.insert(reader.m_data, reader.m_size))
                {
                    writer.add(reader.m_data, reader.m_size);
                }
            }
        }
        tm = clock()-tm;
        printf("time : %.2f (ms)/n", float(tm)*1000.0f/(float)CLOCKS_PER_SEC);
    }

    wprintf(L"press any key to continue.");
    getchar();
}

 

抱歉!评论已关闭.