“价格”是多少?在带有 C++ 的嵌入式环境中使用继承?

发布于 2024-11-29 00:21:57 字数 608 浏览 1 评论 0原文

我正在使用 C++ 开始一个新的嵌入式项目,我想知道使用面向接口的设计是否太昂贵。像这样的东西:

typedef int data;

class data_provider {

public:
    virtual data get_data() = 0;
};

class specific_data_provider : public data_provider {
public:
    data get_data() {
    return 7;
    }
};

class my_device {
public:
    data_provider * dp;
    data d;

    my_device (data_provider * adp) {
    dp = adp;
    d = 0;
    }

    void update() {
    d = dp->get_data();
    }
};

int
main() {
    specific_data_provider sdp;
    my_device dev(&sdp);

    dev.update();

    printf("d = %d\n", dev.d);

    return 0;
}

I'm starting a new embedded project with C++ and I was wondering if it is too much expensive to use a interface oriented design. Something like this:

typedef int data;

class data_provider {

public:
    virtual data get_data() = 0;
};

class specific_data_provider : public data_provider {
public:
    data get_data() {
    return 7;
    }
};

class my_device {
public:
    data_provider * dp;
    data d;

    my_device (data_provider * adp) {
    dp = adp;
    d = 0;
    }

    void update() {
    d = dp->get_data();
    }
};

int
main() {
    specific_data_provider sdp;
    my_device dev(&sdp);

    dev.update();

    printf("d = %d\n", dev.d);

    return 0;
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

爱格式化 2024-12-06 00:21:57

继承本身是免费的。例如,如下所示,从性能/内存的角度来看,BC 是相同的:

struct A { int x; };
struct B : A { int y; };
struct C { int x, y; };

只有当您拥有虚拟函数时,继承才会产生成本。

struct A { virtual ~A(); };
struct B : A { ... };

在这里,在几乎所有实现中,由于虚拟函数的原因,AB 的指针大小都会大一个。

虚函数还具有其他缺点(与非虚函数相比)

  1. 虚函数要求您在调用时查找 vtable。如果该 vtable 不在缓存中,那么您将遇到 L2 未命中,这在嵌入式平台上可能会非常昂贵(例如,在当前一代游戏机上超过 600 个周期)。
  2. 即使您命中 L2 缓存,如果您分支到许多不同的实现,那么您可能会在大多数调用上得到分支错误预测,从而导致管道刷新,这又会花费许多周期。
  3. 由于虚拟函数基本上不可能内联(除了极少数情况),您还会错过许多优化机会。如果您调用的函数很小,那么与内联非虚拟函数相比,这可能会增加严重的性能损失。
  4. 虚拟调用会导致代码膨胀。每个虚拟函数调用都会添加几个字节的指令来查找 vtable,以及用于 vtable 本身的许多字节。

如果您使用多重继承,那么事情会变得更糟。

通常人们会告诉您“在分析器告诉您之前不要担心性能”,但如果性能对您来说非常重要,那么这是一个糟糕的建议。如果您不担心性能,那么最终会出现到处都是虚拟函数的情况,并且当您运行探查器时,没有一个热点需要优化——整个代码库都需要优化。

我的建议是,如果性能对您很重要,则进行设计。如果可能的话,设计时应避免使用虚拟函数。围绕缓存设计数据:更喜欢数组而不是基于节点的数据结构,例如 std::liststd::map。即使您有一个包含数千个元素的容器,并且经常在中间插入,我仍然会在某些架构上选择数组。复制插入数据时丢失的数千个周期很可能会被每次遍历时实现的缓存局部性所抵消(还记得单个 L2 缓存未命中的成本吗?您可以预期其中很多当遍历链表时)

Inheritance, on its own, is free. For example, below, B and C are the same from a performance/memory point of view:

struct A { int x; };
struct B : A { int y; };
struct C { int x, y; };

Inheritance only incurs a cost when you have virtual functions.

struct A { virtual ~A(); };
struct B : A { ... };

Here, on virtually all implementations, both A and B will be one pointer size larger due to the virtual function.

Virtual functions also have other drawbacks (when compared with non-virtual functions)

  1. Virtual functions require that you look up the vtable when called. If that vtable is not in the cache then you will get an L2 miss, which can be incredibly expensive on embedded platforms (over 600 cycles on current gen game consoles for example).
  2. Even if you hit the L2 cache, if you branch to many different implementations then you will likely get a branch misprediction on most calls, causing a pipeline flush, which again costs many cycles.
  3. You also miss out on many optimisation opportunities due to virtual functions being essentially impossible to inline (except in rare cases). If the function you call is small then this could add a serious performance penalty compared to a inlined non-virtual function.
  4. Virtual calls can contribute to code bloat. Every virtual function call adds several bytes worth of instructions to lookup the vtable, and many bytes for the vtable itself.

If you use multiple inheritance then things get worse.

Often people will tell you "don't worry about performance until your profiler tells you to", but this is terrible advice if performance is at all important to you. If you don't worry about performance then what happens is that you end up with virtual functions everywhere, and when you run the profiler, there is no one hotspot that needs optimising -- the whole code base needs optimising.

My advice would be to design for performance if it is important to you. Design to avoid the need for virtual functions if at all possible. Design your data around the cache: prefer arrays to node-based data structures like std::list and std::map. Even if you have a container of a few thousand elements with frequent insertions into the middle, I would still go for an array on certain architectures. The several thousand cycles you lose copying data for the insertions may well be offset by the cache locality you will achieve on each traversal (Remember the cost of a single L2 cache miss? You can expect a lot of those when traversing a linked list)

薄情伤 2024-12-06 00:21:57

继承基本上是免费的。然而,多态性和动态分派(虚拟)会带来一些后果:具有虚拟方法的类的每个实例都包含一个指向vtable的指针,该指针用于选择正确的方法。方法来调用。这为每个虚拟方法调用增加了两次内存访问。

在大多数情况下,这不会成为问题,但它可能成为某些实时应用程序的瓶颈。

Inheritance is basically free. However, polymorphism and dynamic dispatch (virtual) have some consequences: each instance of a class with a virtual method contains a pointer to the vtable, which is used to select the right method to call. This adds two memory access for each virtual method call.

In most cases it won't be a problem, but it can become a bottleneck in some real time applications.

烧了回忆取暖 2024-12-06 00:21:57

确实取决于您的硬件。继承本身可能不会花费您任何费用。虚拟方法会为每个类中的 vTable 消耗一定量的内存。打开异常处理可能会消耗更多的内存和性能。我在 NetBurner 平台上广泛使用了 C++ 的所有功能,其中包括 MOD5272 等芯片,该芯片具有几兆闪存和 8 兆 RAM。另外,有些事情可能与实现相关,取决于我使用的 GCC 工具链,当使用 cout 而不是 printf 时,您会占用大量内存(它似乎链接到一堆库)。您可能对我撰写的关于成本的博客文章感兴趣类型安全代码。您必须在您的环境中运行类似的测试才能真正回答您的问题。

Really depends on your hardware. Inheritance per se probably doesn't cost you anything. Virtual methods cost you some amount of memory for the vTable in each class. Turning on exception handling probably costs you even more in both memory and performance. I have used all the features of C++ extensively on the NetBurner platform with chips like the MOD5272 which have a couple of Megs of Flash and 8 Megs of RAM. Also some things may be implementation dependent, on the GCC toolchain I use, when cout gets used instead of printf you take a big memory hit (it appears to link in a bunch of libraries). You might be interested in a blog post I wrote on the cost of type safe code. You would have to run similar tests on your environment to truly answer your question.

写下不归期 2024-12-06 00:21:57

通常的建议是使代码清晰和正确,然后只有在实践中证明是一个问题(太慢或内存太多)时才考虑优化。

The usual advice is to make the code clear and correct, and then think about optimisations only if it proves to be a problem (too slow or too much memory) in practice.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文