amazzzzzing

导航

大量创建对象的性能

大量创建对象的性能

注:试验中,均统一开启 O3 优化。试验均在同一系统上进行。

背景

在构建一种通用图片对象时,一种可能的实现是将每个像素作为一个对象处理。

以2560*1440为例,约\(3.6\times 10^6\)像素,因此在构建图片时,需要创建大量像素对象。

本文讨论C++下创建大量对象的性能以及是否有优化空间。

试验-malloc

使用memset初始化

int main()
{
    int obj_num = 1 * 1000 * 1000;
    
    int64_t begin = usec_now();
    // empty op
    int64_t end = usec_now();

    printf("empty: %d\n", (int)(end - begin));
    
    begin = usec_now();
    int *a = (int*)malloc(obj_num * sizeof(int));
    end = usec_now();

    printf("malloc: %d\n", (int)(end - begin));

    begin = usec_now();
    memset (a, 0x7, sizeof(obj_num * sizeof(int)));
    end = usec_now();

    printf("memset: %d\n", (int)(end - begin));
    
    begin = usec_now();
    free(a);
    end = usec_now();

    printf("free: %d\n", (int)(end - begin));
    
    return 0;
}

运行结果:

test@ubuntu:~/tmp$ g++ test.cpp -std=c++11 -O3
test@ubuntu:~/tmp$ ./a.out
empty: 0
malloc: 0
memset: 0
free: 0

使用for初始化

int main()
{
    int obj_num = 1 * 1000 * 1000;
    
    int64_t begin = usec_now();
    // empty op
    int64_t end = usec_now();

    printf("empty: %d\n", (int)(end - begin));
    
    begin = usec_now();
    int *a = (int*)malloc(obj_num * sizeof(int));
    end = usec_now();

    printf("malloc: %d\n", (int)(end - begin));

    begin = usec_now();
    for (int i = 0; i < obj_num; ++i)
    {
        a[i] = 0x7;
    }
    end = usec_now();

    printf("memset: %d\n", (int)(end - begin));
    
    begin = usec_now();
    free(a);
    end = usec_now();

    printf("free: %d\n", (int)(end - begin));
    
    return 0;
}

运行结果如下:

test@ubuntu:~/tmp$ g++ test.cpp -std=c++11 -O3
test@ubuntu:~/tmp$ ./a.out
empty: 0
malloc: 6
memset: 1606
free: 426

结论

部分平台的memset有汇编优化,可以用来快速初始化大量内存;

试验-new

使用new[]创建大量默认构造和析构函数对象

试验代码如下:

// g++ test.cpp -std=c++11 -O3

#include <stdio.h>
#include <vector>
#include <stdint.h>
#include <time.h>
#include <chrono>

int64_t usec_now()
{
    auto now = std::chrono::steady_clock::now();
    auto usec = std::chrono::time_point_cast<std::chrono::microseconds>(now).time_since_epoch().count();
    return usec;
}

// 创建大量对象并计时
class Obj
{
public: 
    // Obj() {};
    // ~Obj() {};
private:
    int i;
};

int main()
{
    int obj_num = 1 * 1000 * 1000;
    
    int64_t begin = usec_now();
    // empty op
    int64_t end = usec_now();

    printf("empty: %d\n", (int)(end - begin));
    
    begin = usec_now();
	Obj *objs = new Obj[obj_num];
    end = usec_now();
    printf("construct: %d\n", (int)(end - begin));
    
    begin = usec_now();
	delete [] objs;
    end = usec_now();
    printf("destruct: %d\n", (int)(end - begin));
    
    return 0;
}

运行结果如下:

test@ubuntu:~/tmp$ g++ test.cpp -std=c++11 -O3
test@ubuntu:~/tmp$ ./a.out
empty: 1
construct: 8
destruct: 8

使用new[]创建有空构造函数和默认析构函数对象

class Obj
{
public: 
    Obj() {};
    // ~Obj() {};
private:
    int i;
};

运行结果:

test@ubuntu:~/tmp$ g++ test.cpp -std=c++11 -O3
test@ubuntu:~/tmp$ ./a.out
empty: 0
construct: 7
destruct: 7

使用new[]创建默认构造函数和有空析构函数对象

class Obj
{
public: 
    // Obj() {};
    ~Obj() {};
private:
    int i;
};

运行结果:

test@ubuntu:~/tmp$ g++ test.cpp -std=c++11 -O3
test@ubuntu:~/tmp$ ./a.out
empty: 0
construct: 7
destruct: 602

使用new[]创建有空构造函数和有空析构函数对象

class Obj
{
public: 
    Obj() {};
    ~Obj() {};
private:
    int i;
};

运行结果:

test@ubuntu:~/tmp$ g++ test.cpp -std=c++11 -O3
test@ubuntu:~/tmp$ ./a.out
empty: 0
construct: 6
destruct: 567

使用new[]创建,构造时指定默认值

class Obj
{
public: 
    Obj() {};
    ~Obj() {};
private:
    int i{ 0 };
};

运行结果:

test@ubuntu:~/tmp$ g++ test.cpp -std=c++11 -O3
test@ubuntu:~/tmp$ ./a.out
empty: 0
construct: 1934
destruct: 913

使用new直接创建int数组

int main()
{
    int obj_num = 1 * 1000 * 1000;
    
    int64_t begin = usec_now();
    // empty op
    int64_t end = usec_now();

    printf("empty: %d\n", (int)(end - begin));
    
    begin = usec_now();
	// Obj *objs = new Obj[obj_num];
    int *arr = new int[obj_num];
    end = usec_now();
    printf("construct: %d\n", (int)(end - begin));
    
    begin = usec_now();
	// delete [] objs;
    delete [] arr;
    end = usec_now();
    printf("destruct: %d\n", (int)(end - begin));
    
    return 0;
}

运行结果:

test@ubuntu:~/tmp$ g++ test.cpp -std=c++11 -O3
test@ubuntu:~/tmp$ ./a.out
empty: 0
construct: 7
destruct: 7

使用new直接创建int数组并初始化

int main()
{
    int obj_num = 1 * 1000 * 1000;
    
    int64_t begin = usec_now();
    // empty op
    int64_t end = usec_now();

    printf("empty: %d\n", (int)(end - begin));
    
    begin = usec_now();
    int *a = new int[obj_num]{0x7};
    end = usec_now();

    printf("construct: %d\n", (int)(end - begin));
    
    begin = usec_now();
    delete [] a;
    end = usec_now();

    printf("destruct: %d\n", (int)(end - begin));
    
    return 0;
}

运行结果:

test@ubuntu:~/tmp$ g++ test.cpp -std=c++11 -O3
test@ubuntu:~/tmp$ ./a.out
empty: 0
construct: 1806
destruct: 240

结论

指定有内容的构造函数之后,运行时间相比直接申请内存约有100倍的增加。

对象的构造及初始化和直接使用new创建并初始化的时间复杂度是同级别的。

对象的构造及初始化和使用for进行初始化的时间复杂度是同级别的。

试验-vector

生成int数组

int main()
{
    int obj_num = 1 * 1000 * 1000;
    
    int64_t begin = usec_now();
    // empty op
    int64_t end = usec_now();

    printf("empty: %d\n", (int)(end - begin));
    
    begin = usec_now();
    std::shared_ptr<std::vector<int>> vec = std::make_shared<std::vector<int>>(obj_num);
    end = usec_now();

    printf("construct: %d\n", (int)(end - begin));
    
    begin = usec_now();
    vec.reset();
    end = usec_now();

    printf("destruct: %d\n", (int)(end - begin));
    
    return 0;
}

运行结果:

test@ubuntu:~/tmp$ ./a.out
empty: 0
construct: 1698
destruct: 415

生成对象数组

int main()
{
    int obj_num = 1 * 1000 * 1000;
    
    int64_t begin = usec_now();
    // empty op
    int64_t end = usec_now();

    printf("empty: %d\n", (int)(end - begin));
    
    begin = usec_now();
    std::shared_ptr<std::vector<Obj>> vec = std::make_shared<std::vector<Obj>>(obj_num);
    end = usec_now();

    printf("construct: %d\n", (int)(end - begin));
    
    begin = usec_now();
    vec.reset();
    end = usec_now();

    printf("destruct: %d\n", (int)(end - begin));
    
    return 0;
}

运行结果:

test@ubuntu:~/tmp$ g++ test.cpp -std=c++11 -O3
test@ubuntu:~/tmp$ ./a.out
empty: 0
construct: 1812
destruct: 240

结论

使用vector生成int数组和对象数组,时间复杂度与for初始化级别相同。

优化方案和结论

创建大量对象时,其时间复杂度和直接申请这些对象的内存并初始化的时间复杂度接近,并且使用容器去创建也是一样的时间复杂度。

如果需要提高大量对象创建的性能,一种可能的方法是使用已有的memset来进行初始化。如下:

template <typename _Size>
struct Pixel
{
    uint8_t mem[_Size];
    int size();
    int color(int index);
};

int main()
{
    using Pixel4 = Pixle<4>;
    std::size_t size = sizeof(Pixel4) * width * height;
	Pixel4 *pixel = malloc(size);
    memset(pixel, 0, size);
    
    return 0;
}

值得注意的是,此时被初始化的对象的成员必须是能够被memset的。

posted on 2023-07-10 12:11  amazzzzzing  阅读(11)  评论(0)    收藏  举报