Liste des Groupes | Revenir à cl c |
On 26/05/2024 00:58, Keith Thompson wrote:David Brown <david.brown@hesbynett.no> writes:>On 25/05/2024 03:29, Keith Thompson wrote:Why would it compile faster? #embed expands to something similar toKeith Thompson <Keith.S.Thompson+u@gmail.com> writes:>David Brown <david.brown@hesbynett.no> writes:I've built this from source, and it mostly works. I haven't seen itOn 23/05/2024 14:11, bart wrote:[...][...]'embed' was discussed a few months ago. I disagreed with the poor>
way it was to be implemented: 'embed' notionally generates a list of
comma-separated numbers as tokens, where you have to take care of
any trailing zero yourself if needed. It would also be hopelessly
inefficient if actually implemented like that.
Fortunately, it is /not/ actually implemented like that - it is only
implemented "as if" it were like that. Real prototype implementations
(for gcc and clang - I don't know about other tools) are extremely
efficient at handling #embed. And the comma-separated numbers can be
more flexible in less common use-cases.
>
I'm aware of a proposed implementation for clang:
>
https://github.com/llvm/llvm-project/pull/68620
https://github.com/ThePhD/llvm-project
>
I'm currently cloning the git repo, with the aim of building it so I can
try it out and test some corner cases. It will take a while.
>
I'm not aware of any prototype implementation for gcc. If you are, I'd
be very interested in trying it out.
>
(And thanks for starting this thread!)
do
any optimization; the `#embed` directive expands to a sequence of
comma-separated integer constants.
Which means that this:
#include <stdio.h>
int main(void) {
struct foo {
unsigned char a;
unsigned short b;
unsigned int c;
double d;
};
struct foo obj = {
#embed "foo.dat"
};
printf("a=%d b=%d c=%d d=%f\n", obj.a, obj.b, obj.c, obj.d);
}
given "foo.dat" containing bytes with values 1, 2, 3, and 4,
produces
this output:
a=1 b=2 c=3 d=4.000000
That is what you would expect by the way #embed is specified. You
would not expect to see any "optimisation", since optimisations should
not change the results (apparent from choosing between alternative
valid results).
>
Where you will see the optimisation difference is between :
>
const int xs[] = {
#embed "x.dat"
};
>
and
>
const int xs[] = {
#include "x.csv"
};
>
>
where "x.dat" is a large binary file, and "x.csv" is the same data as
comma-separated values. The #embed version will compile very much
faster, using far less memory. /That/ is the optimisation.
CSV, which still has to be parsed.
No, it does /not/. That's the /whole/ point of #embed, and the main
motivation for its existence. People have always managed to embed
binary source files into their binary output files - using linker
tricks, or using xxd or other tools (common or specialised) to turn
binary files into initialisers for constant arrays (or structs). I've
done so myself on many projects, all integrated together in makefiles.
>
#embed has two purposes. One is to save you from using external tools
for that kind of thing. The other is to do it more efficiently for
big files.
>
There are two ways this is done for examples like this. One is that
is that the compiler does /not/ turn each byte into a series of ASCII
digits for the number, then parse that number to get back to a byte.
It jumps straight from byte in to byte out, possibly after expanding
to a bigger type size if necessary. Secondly, compilers typically
track lots more information about each initialiser - such as the file,
line and column number so that it can give you helpful messages if
there is a value out of range, or too many or too few initialisers.
With #embed, the compiler doesn't have to do any of that.
>
The compiler will generate results /as if/ it had expanded the file to
a list of numbers and parsed them. But it will not do that in
practice. (At least, not for more serious implementations - simple
solutions might do so to get support implemented quickly.)
Reference:>
<https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3220.pdf>
6.10.4.
The first one will probably initialize each int element of xs to a
single byte value extracted from x.dat. Is that what you intended?
Yes, if that's what the programmer wrote - though I agree that
character types will be more common and will be the prime target for
optimisation.
>#embed works best with arrays of unsigned char.>
Sure, that will be a very common use.
>If you mean that the #embed will expand to something other than the>
sequence of integer constants, how does it know to do that in this
context?
It knows because the compiler writers are actually quite smart. The C
standards may describe the translation process in a series of distinct
and independent phases, but that's not how it is done in practice.
The key point is that the compiler knows how the sequence of integers
is going to be used before it gets that far in the preprocessing.
>
I'd expect implementations to have extremely fast implementations for
initialising arrays of character types, and probably also for other
arrays of scaler types. More complicated examples - such as
parameters in a macro or function call - would probably use a
fall-back of generating naïve lists of integer constants.
If you have a binary file containing a sequence of int values, you>
can
use #embed to initialize an unsigned char array that's aliased with or
copied to the int array.
The *embed element width* is typically going to be CHAR_BIT bits by
default. It can only be changed by an *implementation-defined* embed
parameter. It seems odd that there's no standard way to specify the
element width.
It seems even more odd that the embed element width is
implementation defined and not set to CHAR_BIT by default.
I agree. But it may be left flexible for situations where the host
and target have different ideas about CHAR_BIT. (Targets with
CHAR_BIT other than 8 are very rare, hosts with CHAR_BIT other than 8
are non-existent, but C remains flexible.)
A conforming implementation could set the embed element width to,>
say, 4*CHAR_BIT and then not provide an implementation-defined embed
parameter to specify a different width, making #embed unusable for
unsigned char arrays. (N3220 is a draft, not the final C23 standard,
but I haven't heard about any changes in this area.)
The kind of optimization I was thinking about was having #embed, in
some
cases, expand to something other than the specified sequence of
comma-separated integer constants. Such an optimization would be
intended to improve compile-time speed and memory usage, not run-time
performance.
With a straightforward implementation, the preprocessor has to
generate
a sequence of integer constants as text, and then later compiler phases
have to parse that text sequence and generate the corresponding code.
Given:
const unsigned char data[4] = {
#embed "four_bytes.dat"
}
That 4 byte data file is translated to something like "1, 2, 3,
4", then
converted into a stream of tokens, then those tokens are parsed, then,
given the context, the original 4-byte sequence is written into the
generated object file.
For a very large file, that could be a significant burden. (I don't
have any numbers on that.)
I do :
>
<https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3017.htm#design-efficiency-metrics>
>
(That's from a proposal for #embed for C and C++. Generating the
numbers and parsing them is akin to using xxd.)
>
More useful links:
>
<https://thephd.dev/embed-the-details#results>
<https://thephd.dev/implementing-embed-c-and-c++>
>
(These are from someone who did a lot of the work for the proposals,
and prototype implementations, as far as I understand it.)
Note that I can't say how much of a difference this will make in real
life. I don't know how often people need to include multi-megabyte
files in their code. It certainly is not at a level where I would
change any of my existing projects from external generator scripts to
using #embed, but I might use it in future projects.
>
>An optimized version might have the preprocessor generate some>
compiler-specific binary output, say something like "@rawdata N"
followed by N bytes of raw data. Later compiler phases recognize the
"@rawdata" construct and directly dump the data into the object file in
the right place. Making #embed generate @rawdata is only part of the
solution; the compiler has to implement @rawdata in a way that allows it
to be used inside an initializer, or perhaps in any other appropriate
context.
That's the idea. In theory, C pre-processors and C compilers are
independent programs with a standardised format between them - in
practice, they are often part of the same binary, and almost
invariably come from the same developers. The "cpp" program may have
to generate standard preprocessed output, and the "cc" program may
have to accept standard preprocessed output, but there is nothing to
stop the pair of programs supporting extended formats that are more
efficient.
>This could be substantially more efficient for something like:>
static const unsigned char data[] = {
#embed "bigfile.dat"
};
Of course it wouldn't handle my test case above. But #embed can
take
parameters, so it could generate the standard sequence by default and
"@rawdata" if you ask for it.
I don't know whether this kind of optimization is worthwhile, i.e.,
whether the straightforward implementation really imposes significant
commpile-time performance penalties that @rawdata or equivalent can
solve. I also don't know whether existing implementations will
implement this kind of optimization (so far they haven't implemented
#embed at all).
Prototypes have been made, and they do have such optimisations. How
things end up in real tools remains to be seen, of course.
Les messages affichés proviennent d'usenet.