显示标签为“学习”的博文。显示所有博文
显示标签为“学习”的博文。显示所有博文

2012年5月17日星期四

std::map的小小失误



std::map的小小失误


# 故事简介

完全属于我的脑子失误,短暂的弱智时刻,导致该失误的诞生,’罪过‘。
由于项目开发,需增加一逻辑:过滤同样的某接口调用的请求,保持内部响应逻辑处理系统不允许存在一个以上同样的请求。
请求内容包括ID和参数,两者可以区分出不同调用请求。
Request = { RequestID,RequestParam } 

# 失误情况

使用 std::multimap 结构做记录。添加、删除、测试(验证)记录条目。
详细定义,如下:

namespace query_lib
{
typedef std::multimap<int, std::pair<std::string, size_t> > request_record_type;
static request_record_type     _request_record;


/// 记录或标记函数功能请求 标记类型 pair<func_id, pair<func_param, record> >
bool add_request_record(const request_record_type::value_type& obj)
{
    /// Lock lock(...);// todo lock
    request_record_type::iterator lower_it = _request_record.upper_bound(obj.first);
    for (; lower_it != _request_record.end(); ++lower_it )
    {
        if (lower_it->first != obj.first)
            break;
        else if ( lower_it->second.first == obj.second.first)
        {
            ++lower_it->second.second;
            return false;
        }
    }
    _request_record.insert(lower_it, obj);
    return true;
}
/// 擦除函数功能请求的记录或标记 标记类型 pair<func_id, pair<func_param, record> >
void del_request_record(const request_record_type::value_type& obj)
{
    /// Lock lock(...);// todo lock
    request_record_type::iterator upper_it = _request_record.upper_bound(obj.first);
    request_record_type::iterator lower_it = _request_record.lower_bound(obj.first);

    for (request_record_type::iterator iter = upper_it; iter != lower_it; ++iter)
    {
        if (iter->first == obj.first && iter->second.first == obj.second.first)
        {
            if (0 != iter->second.second)
            {
                --(iter->second.second);
            }
            else
                assert(false);
        }

    }
}
bool test_request_record(const request_record_type::value_type& obj)
{
    /// Lock lock(...);// todo lock
    request_record_type::iterator upper_it = _request_record.upper_bound(obj.first);
    request_record_type::iterator lower_it = upper_it;
    for (; lower_it != _request_record.end();
        ++lower_it )
    {
        if (lower_it->first != obj.first)
            break;
        else if ( lower_it->second.first == obj.second.first &&
            obj.second.second != 0)
            return true;
    }
    return false;
}

# 问题解析
诶,简单一句话。颠倒了map::lower_bound & map::upper_bound两成员函数的含义。
罪过啊,浪费了设计,测试,调错,修改过程的数小时(3H+)时间啊,而且那时加班时间!
转,Look following ……
---
public member function

map::lower_bound

      iterator lower_bound ( const key_type& x );
const_iterator lower_bound ( const key_type& x ) const;
Return iterator to lower bound
Returns an iterator pointing to the first element in the container whose key does not compare less than x (using the container's comparison object), i.e. it is either equal or greater.

Unlike upper_bound, this member function returns an iterator to the element also if it compares equal to x and not only if it compares greater.


Notice that, internally, all the elements in a map container are always ordered by their keys following the criterion defined by its comparison object, therefore all the elements that follow the one returned by this function will have a key that compares greater than x.
---

举个明显的例子说明吧,如下:
map = { (1,a), (2, b), (2, c), (2, d), (3, e), (4, f) }

map.lower_bound(2) => (2, b)
map.lupper_bound(2) => (3, e)

# 忠告建议

     当你在想好了设计要开始编写算法逻辑的代码时,再次提醒“清晰确认使用的基本元素”!

# 链接

修改后,正确的程序参见链接页面

2012年4月16日星期一

Fwd: C++的std::string的读时也拷贝技术!

转载:http://coolshell.cn/articles/1443.html

===

C++的std::string的读时也拷贝技术!

嘿嘿,你没有看错,我也没有写错,是读时也拷贝技术。什么?我的错,你之前听说写过时才拷贝,嗯,不错的确有这门技术,英文是Copy On Write,简写就是COW,非常’牛’!那么我们就来看看这个’牛’技术的效果吧。

我们先编写一段程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <string>
#include <iostream>
#include <sys/time.h>
 
static long getcurrenttick()
{
    long tick ;
    struct timeval time_val;
    gettimeofday(&time_val , NULL);
    tick = time_val.tv_sec * 1000 + time_val.tv_usec / 1000 ;
    return tick;
}
 
int main( )
{
    string the_base(1024 * 1024 * 10, 'x');
    long begin =  getcurrenttick();
    for( int i = 0 ;i< 100 ;++i ) {
       string the_copy = the_base ;
    }
    fprintf(stdout,"耗时[%d] \n",getcurrenttick() - begin );
}

嗯,一个非常大的字符串,有10M字节的x,并且执行了100此拷贝。编译执行它,非常快,在我的虚拟机甚至不要1个毫秒。

现在我们来对这个string加点料!

1
2
3
4
5
6
7
8
9
int main(void) {
    string the_base(1024 * 1024 * 10, 'x');
    long begin =  getcurrenttick();
    for (int i = 0; i < 100; i++) {
        string the_copy = the_base;
        the_copy[0] = 'y';
    }
    fprintf(stdout,"耗时[%d] \n",getcurrenttick() - begin );
}

现在我们再编译并执行这断程序,居然需要4~5秒!哇!非常美妙的写时才拷贝技术,性能和功能的完美统一。

我们再来看看另外一种情况!

1
2
3
4
string original = "hello";
char & ref = original[0];
string clone = original;
ref = 'y';

我们生成了一个string,并保留了它首字符的引用,然后复制这个string,修改string中的首字符。因为写操作只是直接的修改了内存中的指定位置,这个string就根本不能感知到有写发生,如果写时才拷贝是不成熟的,那么我们将同时会修改original和clone两个string。那岂不是灾难性的结果?幸好上述问题不会发生。clone的值肯定是没有被修改的。看来COW就是非常的牛!

以上都证明了我们的COW技术非常牛!

有太阳就有黑暗,这句说是不是有点耳熟?

1
2
3
4
5
6
7
8
9
int main(void) {
    string the_base(1024 * 1024 * 10, 'x');
    fprintf(stdout,"the_base's first char is [%c]\n",the_base[0] );
    long begin =  getcurrenttick();
    for (int i = 0; i < 100; i++) {
        string the_copy = the_base;
    }
    fprintf(stdout,"耗时[%d] \n",getcurrenttick() - begin );
}

啊,居然也是4~5秒!你可能在想,我只是做了一个读,没有写嘛,这到底是怎么回事?难道还有读时也拷贝的技术!。

不错,为了避免了你通过[]操作符获取string内部指针而直接修改字符串的内容,在你使用了the_base[0]后,这个字符串的写时才拷贝技术就失效了。

C++标准的确就是这样的,C++标准认为,当你通过迭代器或[]获取到string的内部地址的时候,string并不知道你将是要读还是要写。这是它无法确定,为此,当你获取到内部引用后,为了避免不能捕获你的写操作,它在此时废止了写时才拷贝技术!

这样看来我们在使用COW的时候,一定要注意,如果你不需要对string的内部进行修改,那你就千万不要使用通过[]操作符和迭代器去获取字符串的内部地址引用,如果你一定要这么做,那么你就必须要付出代价。当然,string还提供了一些使迭代器和引用失效的方法。比如说push_back,等, 你在使用[]之后再使用迭代器之后,引用就有可能失效了。那么你又回到了COW的世界!比如下面的一个例子

1
2
3
4
5
6
7
8
9
10
11
12
13
int main( )
{
    struct timeval time_val;
    string the_base(1024 * 1024 * 10, 'x');
    long begin = 0 ;
    fprintf(stdout,"the_base's first char is [%c]\n",the_base[0] );
    the_base.push_back('y');
    begin = getcurrenttick();
    for( int i = 0 ;i< 100 ;++i ) {
        string the_copy = the_base ;
    }
    fprintf(stdout,"耗时[%d] \n",getcurrenttick() - begin );
}

一切又恢复了正常!如果对[]返回引用进行了操作又会发生情况呢,有兴趣的朋友可以试试!结果非常令人惊讶。

另外:上述例子是在linux环境下编译的,使用STL是GNU的STL。windows上我用的是vs2003,但是非常明显vs2003一点都不支持COW。

这篇文章出自http://ridiculousfish.com/blog/archives/2009/09/17/i-didnt-order-that-so-why-is-it-on-my-bill-episode-2/ 这里,我使用了它的例子。但是我重新自己组织了内容。

编写这篇文章的同时,我还参考了耗子的《标准C++类string的Copy-On-Write技术》一文


- - - - - - - - - - - -
谢谢,祝你快乐!(自动添加邮件末尾签名)

Junkun Huang
-- EOF --