C++ 有多少足迹? 异常处理添加

发布于 2024-07-16 11:24:41 字数 379 浏览 10 评论 0原文

这个问题对于嵌入式开发尤其重要。 异常处理为生成的二进制输出增加了一些占用空间。 另一方面,毫无例外,错误都需要以其他方式处理,这需要额外的代码,最终也会增加二进制大小。

我对您的经历感兴趣,特别是:

  1. 您的编译器为异常处理添加的平均占用空间是多少(如果您有这样的测量)?
  2. 就二进制输出大小而言,异常处理真的比其他错误处理策略更昂贵吗?
  3. 对于嵌入式开发,您建议采取什么错误处理策略?

请仅将我的问题作为指导。 欢迎任何意见。

附录:是否有人有一种具体的方法/脚本/工具,对于特定的 C++ 对象/可执行文件,将显示编译器生成的代码和专用于异常处理的数据结构占用的已加载内存占用量的百分比?

This issue is important especially for embedded development. Exception handling adds some footprint to generated binary output. On the other hand, without exceptions the errors need to be handled some other way, which requires additional code, which eventually also increases binary size.

I'm interested in your experiences, especially:

  1. What is average footprint added by your compiler for the exception handling (if you have such measurements)?
  2. Is the exception handling really more expensive (many say that), in terms of binary output size, than other error handling strategies?
  3. What error handling strategy would you suggest for embedded development?

Please take my questions only as guidance. Any input is welcome.

Addendum: Does any one have a concrete method/script/tool that, for a specific C++ object/executable, will show the percentage of the loaded memory footprint that is occupied by compiler-generated code and data structures dedicated to exception handling?

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

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

发布评论

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

评论(8

故人的歌 2024-07-23 11:24:41

当异常发生时,将会产生时间开销,这取决于您如何实现异常处理。 但是,据传闻,使用任何其他方法处理应导致异常的事件的严重性将花费同样多的时间。 为什么不使用高度支持的基于语言的方法来处理此类问题呢?

GNU C++ 编译器默认使用零成本模型,即在不发生异常时没有时间开销。

由于有关异常处理代码和本地对象偏移量的信息可以在编译时计算一次,因此此类信息可以保存在与每个函数关联的单个位置,但不能保存在每个 ARI 中。 您实质上消除了每个 ARI 的异常开销,从而避免了将它们推入堆栈的额外时间。 这种方法称为异常处理的零成本模型,前面提到的优化存储称为影子堆栈。 - Bruce Eckel,《C++ 思维》第 2 卷

大小复杂性开销不容易量化,但 Eckel 表示平均为 5% 和 15%。 这将取决于异常处理代码的大小与应用程序代码的大小之比。 如果您的程序很小,那么异常将占二进制文件的很大一部分。 如果您使用零成本模型,则异常将占用更多空间来消除时间开销,因此,如果您关心空间而不是时间,则不要使用零成本编译。

我的观点是,大多数嵌入式系统都有足够的内存,如果您的系统有 C++ 编译器,您就有足够的空间来包含异常。 我的项目使用的 PC/104 计算机有几 GB 辅助内存、512 MB 主内存,因此不存在例外的空间问题 - 不过,我们的微控制器是用 C 语言编程的。我的启发是“如果有一个主流 C++ 编译器它,使用异常,否则使用 C”。

When an exception occurs there will be time overhead which depends on how you implement your exception handling. But, being anecdotal, the severity of an event that should cause an exception will take just as much time to handle using any other method. Why not use the highly supported language based method of dealing with such problems?

The GNU C++ compiler uses the zero–cost model by default i.e. there is no time overhead when exceptions don't occur.

Since information about exception-handling code and the offsets of local objects can be computed once at compile time, such information can be kept in a single place associated with each function, but not in each ARI. You essentially remove exception overhead from each ARI and thus avoid the extra time to push them onto the stack. This approach is called the zero-cost model of exception handling, and the optimized storage mentioned earlier is known as the shadow stack. - Bruce Eckel, Thinking in C++ Volume 2

The size complexity overhead isn't easily quantifiable but Eckel states an average of 5 and 15 percent. This will depend on the size of your exception handling code in ratio to the size of your application code. If your program is small then exceptions will be a large part of the binary. If you are using a zero–cost model than exceptions will take more space to remove the time overhead, so if you care about space and not time than don't use zero-cost compilation.

My opinion is that most embedded systems have plenty of memory to the extent that if your system has a C++ compiler you have enough space to include exceptions. The PC/104 computer that my project uses has several GB of secondary memory, 512 MB of main memory, hence no space problem for exceptions - though, our micorcontrollers are programmed in C. My heuristic is "if there is a mainstream C++ compiler for it, use exceptions, otherwise use C".

盗梦空间 2024-07-23 11:24:41

测量事物,第 2 部分。我现在有两个程序。 第一个是 C 语言,用 gcc -O2 编译:

#include <stdio.h>
#include <time.h>

#define BIG 1000000

int f( int n ) {
    int r = 0, i = 0;
    for ( i = 0; i < 1000; i++ ) {
        r += i;
        if ( n == BIG - 1 ) {
            return -1;
        }
    }
    return r;
}

int main() { 
    clock_t start = clock();
    int i = 0, z = 0;
    for ( i = 0; i < BIG; i++ ) {
        if ( (z = f(i)) == -1 ) { 
            break;
        }
    }
    double t  = (double)(clock() - start) / CLOCKS_PER_SEC;
    printf( "%f\n", t );
    printf( "%d\n", z );
}

第二个是 C++,带有异常处理,用 g++ -O2 编译:

#include <stdio.h>
#include <time.h>

#define BIG 1000000

int f( int n ) {
    int r = 0, i = 0;
    for ( i = 0; i < 1000; i++ ) {
        r += i;
        if ( n == BIG - 1 ) {
            throw -1;
        }
    }
    return r;
}

int main() { 
    clock_t start = clock();
    int i = 0, z = 0;
    for ( i = 0; i < BIG; i++ ) {
        try {
         z += f(i); 
        }
        catch( ... ) {
            break;
        }

    }
    double t  = (double)(clock() - start) / CLOCKS_PER_SEC;
    printf( "%f\n", t );
    printf( "%d\n", z );
}

我认为这些回答了我上一篇文章中提出的所有批评。

结果:执行时间使 C 版本比 C++ 版本有 0.5% 的优势(有例外),而不是其他人谈论过(但未演示)的 10%

我会如果其他人可以尝试编译和运行代码(应该只需要几分钟),以检查我是否在任何地方犯了可怕且明显的错误,我将非常感激。 这就是所谓的“科学方法”!

Measuring things, part 2. I have now got two programs. The first is in C and is compiled with gcc -O2:

#include <stdio.h>
#include <time.h>

#define BIG 1000000

int f( int n ) {
    int r = 0, i = 0;
    for ( i = 0; i < 1000; i++ ) {
        r += i;
        if ( n == BIG - 1 ) {
            return -1;
        }
    }
    return r;
}

int main() { 
    clock_t start = clock();
    int i = 0, z = 0;
    for ( i = 0; i < BIG; i++ ) {
        if ( (z = f(i)) == -1 ) { 
            break;
        }
    }
    double t  = (double)(clock() - start) / CLOCKS_PER_SEC;
    printf( "%f\n", t );
    printf( "%d\n", z );
}

The second is C++, with exception handling, compiled with g++ -O2:

#include <stdio.h>
#include <time.h>

#define BIG 1000000

int f( int n ) {
    int r = 0, i = 0;
    for ( i = 0; i < 1000; i++ ) {
        r += i;
        if ( n == BIG - 1 ) {
            throw -1;
        }
    }
    return r;
}

int main() { 
    clock_t start = clock();
    int i = 0, z = 0;
    for ( i = 0; i < BIG; i++ ) {
        try {
         z += f(i); 
        }
        catch( ... ) {
            break;
        }

    }
    double t  = (double)(clock() - start) / CLOCKS_PER_SEC;
    printf( "%f\n", t );
    printf( "%d\n", z );
}

I think these answer all the criticisms made of my last post.

Result: Execution times give the C version a 0.5% edge over the C++ version with exceptions, not the 10% that others have talked about (but not demonstrated)

I'd be very grateful if others could try compiling and running the code (should only take a few minutes) in order to check that I have not made a horrible and obvious mistake anywhere. This is knownas "the scientific method"!

为人所爱 2024-07-23 11:24:41

我在低延迟环境中工作。 (对于我的应用程序在生产“链”中的时间,不到 300 微秒)根据我的经验,异常处理会增加 5-25% 的执行时间,具体取决于您执行的操作量!

我们通常不关心二进制膨胀,但如果你膨胀太多,那么你就会疯狂地挣扎,所以你需要小心。

只需保持二进制合理(取决于您的设置)。

我对我的系统进行了相当广泛的分析。
其他令人讨厌的领域:

日志记录

持久化(我们只是不做这个,或者如果我们做它是并行的)

I work in a low latency environment. (sub 300 microseconds for my application in the "chain" of production) Exception handling, in my experience, adds 5-25% execution time depending on the amount you do!

We don't generally care about binary bloat, but if you get too much bloat then you thrash like crazy, so you need to be careful.

Just keep the binary reasonable (depends on your setup).

I do pretty extensive profiling of my systems.
Other nasty areas:

Logging

Persisting (we just don't do this one, or if we do it's in parallel)

眼波传意 2024-07-23 11:24:41

我想这取决于该特定平台的硬件和工具链端口。

我没有数据。 然而,对于大多数嵌入式开发,我看到人们扔掉了两件事(对于 VxWorks/GCC 工具链):

  • 模板
  • RTTI

异常处理在大多数情况下确实使用了这两者,因此也有将其扔掉的趋势。

在我们确实想要接近金属的情况下,可以使用 setjmp/longjmp请注意,这可能不是最好的解决方案(或非常强大),但这就是_我们_使用的解决方案。

您可以使用两个版本的基准测试套件(带/不带)在桌面上运行简单的测试异常处理并获取您最可靠的数据。

关于嵌入式开发的另一件事是:像瘟疫一样避免使用模板——它们会导致过多的膨胀。 异常标记沿着模板和 RTTI,如 Johann Gerell 在评论中所解释的那样(我认为这很好理解)。

再说一遍,这就是我们所做的。 这么多的反对票是怎么回事?

I guess it'd depend on the hardware and toolchain port for that specific platform.

I don't have the figures. However, for most embedded developement, I have seen people chucking out two things (for VxWorks/GCC toolchain):

  • Templates
  • RTTI

Exception handling does make use of both in most cases, so there is a tendency to throw it out as well.

In those cases where we really want to get close to the metal, setjmp/longjmp are used. Note, that this isn't the best solution possible (or very powerful) probably, but then that's what _we_ use.

You can run simple tests on your desktop with two versions of a benchmarking suite with/without exception handling and get the data that you can rely on most.

Another thing about embedded development: templates are avoided like the plague -- they cause too much bloat. Exceptions tag along templates and RTTI as explained by Johann Gerell in the comments (I assumed this was well understood).

Again, this is just what we do. What is it with all the downvoting?

黯淡〆 2024-07-23 11:24:41

需要考虑的一件事是:如果您在嵌入式环境中工作,您希望应用程序尽可能小。 Microsoft C 运行时给程序增加了相当多的开销。 通过删除 C 运行时作为要求,我能够得到一个简单的程序,它是 2KB 的 exe 文件,而不是 70 KB 左右的文件,而且这是在打开所有大小优化的情况下实现的。

C++异常处理需要编译器支持,这是由C运行时提供的。 具体细节笼罩在神秘之中,根本没有记录。 通过避免 C++ 异常,我可以删除整个 C 运行时库。

您可能会争论只动态链接,但就我而言,这是不切实际的。

另一个问题是,C++ 异常至少在 MSVC 上需要有限的 RTTI(运行时类型信息),这意味着异常的类型名称存储在可执行文件中。 从空间角度来看,这不是问题,但对我来说,文件中没有这些信息只是“感觉”更干净。

One thing to consider: If you're working in an embedded environment, you want to get the application as small as possible. The Microsoft C Runtime adds quite a bit of overhead to programs. By removing the C runtime as a requirement, I was able to get a simple program to be a 2KB exe file instead of a 70-something kilobyte file, and that's with all the optimizations for size turned on.

C++ exception handling requires compiler support, which is provided by the C runtime. The specifics are shrouded in mystery and are not documented at all. By avoiding C++ exceptions I could cut out the entire C runtime library.

You might argue to just dynamically link, but in my case that wasn't practical.

Another concern is that C++ exceptions need limited RTTI (runtime type information) at least on MSVC, which means that the type names of your exceptions are stored in the executable. Space-wise, it's not an issue, but it just 'feels' cleaner to me to not have this information in the file.

仙女 2024-07-23 11:24:41

很容易看出对二进制大小的影响,只需在编译器中关闭 RTTI 和异常即可。 如果您正在使用dynamic_cast<>,您会收到有关它的投诉...但我们通常避免使用依赖于dynamic_cast<>的代码。 在我们的环境中。

我们始终发现,就二进制大小而言,关闭异常处理和 RTTI 是一种胜利。 我在没有异常处理的情况下见过许多不同的错误处理方法。 最流行的似乎是在调用堆栈中传递失败代码。 在我们当前的项目中,我们使用 setjmp/longjmp 但我建议在 C++ 项目中不要这样做,因为在许多实现中退出作用域时它们不会运行析构函数。 老实说,我认为这是代码原始架构师做出的一个糟糕的选择,特别是考虑到我们的项目是 C++。

It's easy to see the impact on binary size, just turn off RTTI and exceptions in your compiler. You'll get complaints about dynamic_cast<>, if you're using it... but we generally avoid using code that depends on dynamic_cast<> in our environments.

We've always found it to be a win to turn off exception handling and RTTI in terms of binary size. I've seen many different error handling methods in the absence of exception handling. The most popular seems to be passing failure codes up the callstack. In our current project we use setjmp/longjmp but I'd advise against this in a C++ project as they won't run destructors when exiting a scope in many implementations. If I'm honest I think this was a poor choice made by the original architects of the code, especially considering that our project is C++.

绮烟 2024-07-23 11:24:41

在我看来,异常处理对于嵌入式开发来说通常是不可接受的。

GCC 和 Microsoft 都没有“零开销”异常处理。 两个编译器都会将序言和结尾语句插入到跟踪执行范围的每个函数中。 这会导致性能和内存占用量显着增加。

根据我的经验,性能差异约为 10%,这对于我的工作领域(实时图形)来说是一个巨大的数字。 内存开销要少得多,但仍然很重要 - 我记不起这个数字,但使用 GCC/MSVC 可以很容易地以两种方式编译程序并测量差异。

我见过一些人将异常处理视为“仅当您使用它时”的成本。 根据我的观察,这不是真的。 当您启用异常处理时,它会影响所有代码,无论代码路径是否可以引发异常(当您考虑编译器的工作方式时,这完全有意义)。

我也会远离 RTTI 进行嵌入式开发,尽管我们确实在调试版本中使用它来对向下转型结果进行健全性检查。

In my opinion exception handling is not something that's generally acceptable for embedded development.

Neither GCC nor Microsoft have "zero-overhead" exception handling. Both compilers insert prologue and epilogue statements into each function that track the scope of execution. This leads to a measurable increase in performance and memory footprint.

The performance difference is something like 10% in my experience, which for my area of work (realtime graphics) is a huge amount. The memory overhead was far less but still significant - I can't remember the figure off-hand but with GCC/MSVC it's easy to compile your program both ways and measure the difference.

I've seen some people talk about exception handling as an "only if you use it" cost. Based on what I've observed this just isn't true. When you enable exception handling it affects all code, whether a code path can throw exceptions or not (which makes total sense when you consider how a compiler works).

I would also stay away from RTTI for embedded development, although we do use it in debug builds to sanity check downcasting results.

尘世孤行 2024-07-23 11:24:41

定义“嵌入”。 在 8 位处理器上,我肯定不会处理异常(我肯定不会在 8 位处理器上使用 C++)。 如果您使用的 PC104 类型主板功能强大,足以成为几年前某人的台式机,那么您可能会摆脱它。 但我不得不问——为什么会有例外? 通常在嵌入式应用程序中,发生异常之类的事情是不可想象的 - 为什么这个问题没有在测试中得到解决?

例如,这是在医疗设备中吗? 医疗设备中的草率软件已经导致人员死亡。 任何计划外的事情发生都是不可接受的。 所有故障模式都必须考虑在内,正如 Joel Spolsky 所说,异常就像 GOTO 语句,除非您不知道它们是从哪里调用的。 那么,当您处理异常时,发生了什么故障以及您的设备处于什么状态? 由于您的异常,您的放射治疗机是否卡在“满”状态并且正在将某人活活煮熟(这种情况在现实生活中发生过)? 您的 10,000 多行代码中到底在什么时候发生了异常? 当然,您可以将其减少到大约 100 行代码,但是您知道导致异常的每一行的重要性吗?

如果没有更多信息,我会说不要为嵌入式系统中的异常做好准备。 如果添加它们,请准备好计划可能导致异常的每一行代码的故障模式。 如果你正在制造医疗设备,那么如果你不制造,人们就会死亡。 如果您正在制造便携式 DVD 播放器,那么您制造的便携式 DVD 播放器很糟糕。 是哪一个?

Define 'embedded'. On an 8-bit processor I would not certainly not work with exceptions (I would certainly not work with C++ on an 8-bit processor). If you're working with a PC104 type board that is powerful enough to have been someone's desktop a few years back then you might get away with it. But I have to ask - why are there exceptions? Usually in embedded applications anything like an exception occurring is unthinkable - why didn't that problem get sorted out in testing?

For instance, is this in a medical device? Sloppy software in medical devices has killed people. It is unacceptable for anything unplanned to occur, period. All failure modes must be accounted for and, as Joel Spolsky said, exceptions are like GOTO statements except you don't know where they're called from. So when you handle your exception, what failed and what state is your device in? Due to your exception is your radiation therapy machine stuck at FULL and is cooking someone alive (this has happened IRL)? At just what point did the exception happen in your 10,000+ lines of code. Sure you may be able to cut that down to perhaps 100 lines of code but do you know the significance of each of those lines causing an exception is?

Without more information I would say do NOT plan for exceptions in your embedded system. If you add them then be prepared to plan the failure modes of EVERY LINE OF CODE that could cause an exception. If you're making a medical device then people die if you don't. If you're making a portable DVD player, well, you've made a bad portable DVD player. Which is it?

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