by Niall Douglas. Last updated . This page has been accessed 45,612 times since the 17th April 2004.
| View this page in: |
|
Any language: |
|
|
|
|
|
|
|
Translation to non-English languages provided by Google Language
You are connecting to the IPv4 version of this website from the IP address 184.72.184.104. You can try the IPv6-only version if you want.
|
|
It took me an
age to finally delve into GCC and make this patch despite having wanted to
do so for as long as I can remember - however trying to debug the TnFOX
Python bindings based on Boost.Python eventually drove me to despair. In
reality though, I couldn't have done it without Brian Ryner's patch
enabling class & struct symbol visibility declarations (PR
9283) and the new patch presented here could not have happened without
Brian's continued efforts which are documented at PR
15000. This patch was submitted for inclusion to GCC v4.0 on the 10th
May 2004 at http://gcc.gnu.org/ml/gcc-patches/2004-05/msg00571.html
(previous version is: http://gcc.gnu.org/ml/gcc-patches/2004-04/msg00362.html)
but I'll keep this page open until it makes it into a release version of
GCC.
Note: As of 25th July 2004, this patch is now part of GCC v4.0 Quick Start: How to apply support to your library in four easy stepsWhy is this patch so useful?Put simply, it hides most of the ELF symbols which would have previously (and unnecessarily) been public. This means:
Although TnFOX's Python bindings are an extreme case, this patch reduced the exported symbol table from > 200,000 symbols to less than 18,000. Some 21Mb was knocked off the binary size as well! Some people may suggest that GNU linker version scripts can do just as well. Perhaps for C programs this is true, but for C++ it cannot be true - unless you labouriously specify each and every symbol to make public, you must use wildcards which tend to let a lot of spurious symbols through. I found I couldn't get my symbol table below ~40,000 using version scripts. Furthermore, using linker version scripts doesn't permit GCC to better optimise the code. Windows compatibilityFor anyone who has worked on any sizeable portable application on both
Windows and POSIX, you'll know the sense of frustration that non-Windows
builds of GCC don't offer an equivalent to __declspec(dllexport)
ie; the ability to mark your C/C++ interface as being that of the shared
library. I say frustration because good DSO interface design is just as
important for healthy coding as good class design, or correctly opaquing
internal data structures. POSIX programmers generally just don't get this While the semantics can't be the same with Windows DLL's and ELF DSO's, almost all Windows-based code uses a macro to compile-time select whether dllimport or dllexport is being used. This mechanism can be easily reused with this patch so adding support to anything already able to be compiled as a Windows DLL is literally a five minute operation. Note: The semantics are not the same between Windows and this GCC feature - for example, "__declspec(dllexport) void (*foo)(void)" and "void (__declspec(dllexport) *foo)(void)" mean quite different things whereas this generates a warning about not being able to apply attributes to non-types on GCC. Still not convinced?Ok, go
read this article by Ulrich Drepper. He's the lead maintainer behind GNU
glibc, probably the most important library to any Linux based
application. If you're not convinced after reading his arguments, feel
free to go back and live in your cave! How to useGet the sources of GCC v3.4 or v4.0 from CVS and apply the patch below. (Re)compile and install. In your header files, wherever you want an interface or API made public outside the current DSO, place __attribute__ ((visibility("default"))) in struct, class and function declarations you wish to make public (it's easier if you define a macro as this). You don't need to specify it in the definition. Now alter your make system to pass -fvisibility=hidden to each call of GCC compiling a source file. If you are throwing exceptions across shared object boundaries see the section "Caveats" below. Use nm -C -D on the outputted DSO to compare before and after to see the difference it makes. Some examples of the syntax: #ifdef _MSC_VER
#ifdef BUILDING_DLL
#define DLLEXPORT __declspec(dllexport)
#else
#define DLLEXPORT __declspec(dllimport)
#endif
#define DLLLOCAL
#else
#ifdef HAVE_GCCVISIBILITYPATCH
#define DLLEXPORT __attribute__ ((visibility("default")))
#define DLLLOCAL __attribute__ ((visibility("hidden")))
#else
#define DLLEXPORT
#define DLLLOCAL
#endif
#endif
extern "C" DLLEXPORT void function(int a);
class DLLEXPORT SomeClass
{
int c;
DLLLOCAL void privateMethod(); // Only for use within this DSO
public:
Person(int _c) : c(_c) { }
static void foo(int a);
};
A related topic is producing more optimised code - because when you
declare something defined outside the current compiland GCC cannot know if
that symbol resides inside or outside the DSO the current compiland will
eventually end up in, it must assume the worst and route everything
through the GOT (Global Offset Table) which carries overhead both in code
space and extra (costly) relocations for the dynamic linker to perform. To
tell GCC a class, struct, function or variable is defined within the
current DSO you must specify hidden visibility manually within its header
file declaration (using the example above, you declare such things with
DLLLOCAL). This causes GCC to generate optimal code.
Because you are specifying a DSO's interface contract with the outside world, you should always manually specify hidden visibility for everything not available to code outside your DSO, including individual methods in a class (place the attribute at the start like in the example above if you wish to maintain syntax compatibility with Windows). This improves readability of the code and leaves everyone in no doubt as to what the intended use for the API is. However, to aid you converting old code to use the new system, the patch provides a #pragma GCC visibility command: extern void foo(int); #pragma GCC visibility push(hidden) extern void someprivatefunct(int); #pragma GCC visibility popYou should really only use this for legacy code. All new code should specify each declaration as exported or local individually. Lastly, there's one other new command line switch: -fvisibility-inlines-hidden. This causes all inlined class member functions to have hidden visibility, causing significant export symbol table size & binary size reductions though not as much as using -fvisibilty=hidden. However, -fvisibility-inlines-hidden can be used with no source alterations - simply apply and win! Caveats (please read):During extended usage of this patch, certain issues have become apparent:
Download
All regression suite tests pass plus the patch adds some new tests. Furthermore from v0.75 the TnFOX binaries I release were generated using this patch and indeed all my debug builds also use it. Faster load times means faster debugging! All code in the patch is (C) Niall Douglas, Brian Ryner and the FSF. To the best of my knowledge all code in the patch is licensed under the GPL. Quick start:The following instructions are how to add full support to your library, yielding the highest quality code with the greatest reductions in binary size, load times and link times. All new code should have this support from the beginning! and it's worth your while especially in speed critical libraries to spend the few days required to implement it fully - it's a once off investment of time with nothing but good resulting forever more. You can however add basic support to your library in far less time though it is not recommended that you do so.
Well done, you have just made your GCC output binary considerably more optimised!
blog comments powered by Disqus
|