View previous topic :: View next topic |
Author |
Message |
Akkara Bodhisattva
Joined: 28 Mar 2006 Posts: 6702 Location: &akkara
|
Posted: Fri May 17, 2013 6:24 am Post subject: Getting g++ to compile constant objects directly into memory |
|
|
Here's a problem I keep coming back to, unable to find a good answer for.
Here's a sample program to show the problem: Code: | class Color {
private:
unsigned c;
public:
Color(int r = 0, int g = 0, int b = 0)
: c( (r & 0xFF) + 256*(g & 0xFF) + 65536*(b & 0xFF) )
{}
Color &red(int r)
{
c &= ~0xFF;
c |= (r & 0xFF);
return *this;
}
Color &green(int g)
{
c &= ~(256 * 0xFF);
c |= 256 * (g & 0xFF);
return *this;
}
Color &blue(int b)
{
c &= ~(65536 * 0xFF);
c |= 65536 * (b & 0xFF);
return *this;
}
};
static const Color color_tab[] = {
Color().red(0xFF),
Color().red(0xFF).green(0xC0),
Color().red(0xC0).green(0xFF),
Color().green(0xFF),
Color().green(0xFF).blue(0xC0),
Color().green(0xC0).blue(0xFF),
Color().blue(0xFF),
Color().blue(0xFF).red(0xC0),
Color().blue(0xC0).red(0xFF)
};
static const Color color_tab2[] = { // Try it with the constructor alone in case that makes a difference
Color(0xFF,0x00,0x00),
Color(0xFF,0xC0,0x00),
Color(0xC0,0xFF,0x00)
}; |
There's a static const global table filled with simple objects. The constructors are all simple. Just a bunch of arithmetic operations on integers. No file-opening or pointers or allocations or virtuals or anything complicated like that.
It ought to be possible to compile these tables directly into memory.
But it doesn't.
It gets almost the whole way there: it resolves to the final constant values that are needed. But instead of sticking those into memory, it instead emits a procedure consisting of long chains of move X into memory at address A. This is the result when compiled with g++ -O2 -S: Code: | .section .text.startup,"ax",@progbits
.p2align 4,,15
.type _GLOBAL__sub_I_z_try2.c__, @function
_GLOBAL__sub_I_z_try2.c__:
.LFB7:
.cfi_startproc
movl $255, _ZL9color_tab(%rip)
movl $49407, _ZL9color_tab+4(%rip)
movl $65472, _ZL9color_tab+8(%rip)
movl $65280, _ZL9color_tab+12(%rip)
movl $12648192, _ZL9color_tab+16(%rip)
movl $16760832, _ZL9color_tab+20(%rip)
movl $16711680, _ZL9color_tab+24(%rip)
movl $16711872, _ZL9color_tab+28(%rip)
movl $12583167, _ZL9color_tab+32(%rip)
movl $255, _ZL10color_tab2(%rip)
movl $49407, _ZL10color_tab2+4(%rip)
movl $65472, _ZL10color_tab2+8(%rip)
ret
.cfi_endproc
.size _GLOBAL__sub_I_z_try2.c__, .-_GLOBAL__sub_I_z_try2.c__
.section .init_array,"aw"
.align 8
.quad _GLOBAL__sub_I_z_try2.c__
.local _ZL9color_tab
.comm _ZL9color_tab,36,32
.local _ZL10color_tab2
.comm _ZL10color_tab2,12,4 | Why does it do such a silly thing?
All those constants like $49407 could have been put directly into the _ZL9color_tab memory area using assembler initialized-storage directives, without using a procedure to act as an intermediary. Instead, this takes around 3x the space that would otherwise be required: the data itself is in two places (space for the table itself, and an equivalent amount of space in all those $-prefixed numbers serving as initializers), plus there's the load and store opcodes, the addressing offsets, and the procedure call/return.
(I also tried it with char r, g, b as the private data members, in case the arithmetic was causing problems. That result is even worse.)
I know, memory is cheap and all that. But what if it's a more constrained embedded system. Or perhaps it's a big table. It's stuff like this that's keeping me wedded to ugly #define macro constants and C-like code instead of finally making the long-overdue switch to a more object-oriented style.
Perhaps there's an option or language feature I'm not aware of? Maybe a newer compiler finally fixes it? I'm currently trying it on g++ 4.7.2. But I've been trying versions starting around 4.3 or 4.4 and the results have been similar with each.
Ideas? |
|
Back to top |
|
|
MustrumR n00b
Joined: 15 Nov 2011 Posts: 71 Location: Right here
|
Posted: Fri May 17, 2013 3:47 pm Post subject: |
|
|
Try making the constructor constexpr. |
|
Back to top |
|
|
John R. Graham Administrator
Joined: 08 Mar 2005 Posts: 10587 Location: Somewhere over Atlanta, Georgia
|
Posted: Fri May 17, 2013 4:14 pm Post subject: |
|
|
I doubt that will help as, according to the generated assembler snippet Akkara provided, the compiler is already resolving each constructor / method invocation to an integer constant. It's not generating code to calculate the value to fill but only to do the actual fill.
- John _________________ I can confirm that I have received between 0 and 499 National Security Letters. |
|
Back to top |
|
|
Akkara Bodhisattva
Joined: 28 Mar 2006 Posts: 6702 Location: &akkara
|
Posted: Sat May 18, 2013 3:30 am Post subject: |
|
|
MustrumR wrote: | Try making the constructor constexpr. |
Thanks for that suggestion! It seems like it leads somewhere.
Specifically, the constructor-initialized table gets filled directly, no longer through an intermediate procedure. But for that to happen, in addition to constexpr, it seems the table also has to be declared extern... Which in regular code appears in the header file anyway, unlike the ill-chosen static in this abbreviated example.
The build-it-by-parts version can't seem to use constexpr - apparently assignment isn't allowed in a procedure declared that way.
I'll take some more playing with it before I can write more definitively. But thanks again for this. It is the first time I've seen a compile-time-instantiable object actually get instantiated right down to the in-memory bit-patterns, using no extra support code. This is a big deal in embedded systems where you'll often have plenty of ROM but only 4 or 8 K (not M) of RAM. And although the table is a const and could well be in ROM, the old initialize-with-procedure style requires it to use up valuable RAM just so the initializer can write to it. |
|
Back to top |
|
|
|
|
You cannot post new topics in this forum You cannot reply to topics in this forum You cannot edit your posts in this forum You cannot delete your posts in this forum You cannot vote in polls in this forum
|
|