The case for a good string API
Hi. I will try again proposing a good string API for the project. But this time, instead of showing the implementation code, I will ask about the principle. So, please, read the argument, and give your opinion on the principle. If we agree on the principle, we can discuss the code then. * Why we need a better string API Strings are not the bulk of what we do, but for the user interface of things we have a LOT of functions that return strings: to describe a color space or a channel layout or a set of options. When the returned string is short, we use the good old “char *buf, size_t buf_size”. When it is long, we return a dynamically-allocated buffer. When the string is almost always short but can actually be very long, and the conversion is used in speed-critical code, we are screwed. With a good string API, we could use the same API everywhere, it would automatically use the most efficient memory scheme. Furthermore, if all our object→string functions have the same prototype, it makes it easier to use these functions as standardized callbacks and opens a whole lot of new possibilities of serialization features. Moreover, a good string API would bring quite a few extra benefits, with a few examples: easier to build strings from parts, less verbose and more robust error checking, the ability to use directly the string or buffering API of another library or language. So, even though strings are not the core of our thing, I think we should have a good string API, just like we have an API for efficient FFTs (which is the core of our thing). But let us discuss it further: * Objections and counter-objections - A string API adds complexity. Indeed, that is true. But the added complexity is clearly isolated in one or a few source files. In the rest of the code, it removes complexity (see less verbose error checking, easier to build strings from parts above). String conversion functions can happen once per short frame, for example in the ashowinfo filter. We have pools for frames and for buffers: we already consider that avoid once-per-frame memory allocations is worth the added complexity. A string API is just a step in the same direction. - An unusual API is annoying for people who will use it. Unless we messed up, if they use strings more than just a little, the benefits of the API is way worth the cost of learning. But some people use strings just a little. For them, who would be happy with a plain C string, we should make sure the required learning is very very limited. And we can: “instead of ‘buf, sizeof(buf)’, write ‘string_from_buffer(buf)”. That is all, in one short line I have documented how to use the API with the old patterns. There is a little more to write for people who want a dynamic buffer for an unlimited string (error checks are always annoying), but we can keep it under three minutes. * What string API we should use We have AVBprint, but the API is ugly, and it only brings a few of the benefits I promised. I have a proposal: AVWriter. I posted it last spring, here is an introduction on how to use it (with the implementation in the same thread): https://ffmpeg.org/pipermail/ffmpeg-devel/2021-April/279383.html * Conclusion Well, I am convinced, but what about you? 1. Do you think FFmpeg needs a good string API? If not, please explain why? 2. Assuming we agree ‘yes’ on the previous question, do you think AVWriter is a good candidate? If not, what else would you propose? Regards, -- Nicolas George
On date Wednesday 2021-12-22 17:12:41 +0100, Nicolas George wrote:
Hi.
I will try again proposing a good string API for the project. But this time, instead of showing the implementation code, I will ask about the principle.
So, please, read the argument, and give your opinion on the principle. If we agree on the principle, we can discuss the code then. [...] * What string API we should use
We have AVBprint, but the API is ugly, and it only brings a few of the benefits I promised. I have a proposal: AVWriter.
I posted it last spring, here is an introduction on how to use it (with the implementation in the same thread):
https://ffmpeg.org/pipermail/ffmpeg-devel/2021-April/279383.html
* Conclusion
Well, I am convinced, but what about you?
1. Do you think FFmpeg needs a good string API? If not, please explain why?
2. Assuming we agree ‘yes’ on the previous question, do you think AVWriter is a good candidate? If not, what else would you propose?
Hi, and sorry for the long delay (I'll comment soon about the AVWriter API). Before jumping to the discussion, probably it's good to think a bit about the bprint.h API and its limitations (the ones which come to mind are: no errors in case of truncation, and possible inefficiency due to the realloc). So while it covers the case for small strings (and it's not that bad IMO from the API point of view), probably it's underkill for data serialization. Do you have more in mind about its limitations? Also, is the new API supposed to be a replacement for AVBprint or is it supposed to live in parallel with it (to serve different purposes)?
Stefano Sabatini (12022-06-15):
Hi, and sorry for the long delay (I'll comment soon about the AVWriter API).
Thanks. I was starting to despair that somebody else gives a damn about it.
Before jumping to the discussion, probably it's good to think a bit about the bprint.h API and its limitations (the ones which come to mind are: no errors in case of truncation, and possible inefficiency due to the realloc). So while it covers the case for small strings (and it's not that bad IMO from the API point of view), probably it's underkill for data serialization.
Do you have more in mind about its limitations?
The limitations of BPrint were one of the motivations to start working on this. But I am not sure what you are referring to exactly. BPrint already has a means to test for truncation in case of memory allocation failure. I tried to make it a little harder to forget with dynamic AVWriter, but it is a fine line between harder to forget and annoying to use. Regarding realloc(), I am even more confused: we cannot avoid a dynamic allocation if the string is larger than the initial buffer. But AVWriter goes a step further in that direction: if you want to write to a file or to the network, it can do so on the fly, without using a dynamic buffer.
Also, is the new API supposed to be a replacement for AVBprint or is it supposed to live in parallel with it (to serve different purposes)?
Eventually, I would like to have all useful features of BPrint in the dynamic buffer AVWriter and deprecate BPrint, since that was written in part to learn and do BPrint better. Regards, -- Nicolas George
Hi Nicolas! On date Thursday 2022-06-16 17:47:40 +0200, Nicolas George wrote:
Stefano Sabatini (12022-06-15):
Hi, and sorry for the long delay (I'll comment soon about the AVWriter API).
Thanks. I was starting to despair that somebody else gives a damn about it.
Before jumping to the discussion, probably it's good to think a bit about the bprint.h API and its limitations (the ones which come to mind are: no errors in case of truncation, and possible inefficiency due to the realloc). So while it covers the case for small strings (and it's not that bad IMO from the API point of view), probably it's underkill for data serialization.
Do you have more in mind about its limitations?
The limitations of BPrint were one of the motivations to start working on this. But I am not sure what you are referring to exactly.
BPrint already has a means to test for truncation in case of memory allocation failure. I tried to make it a little harder to forget with dynamic AVWriter, but it is a fine line between harder to forget and annoying to use.
Right, I completely forgot about it.
Regarding realloc(), I am even more confused: we cannot avoid a dynamic allocation if the string is larger than the initial buffer. But AVWriter goes a step further in that direction: if you want to write to a file or to the network, it can do so on the fly, without using a dynamic buffer.
I was thinking about mempool (and no, I don't think it's really neeeded for this use case). I still had to read the implementation, now I think I got what this is about.
Also, is the new API supposed to be a replacement for AVBprint or is it supposed to live in parallel with it (to serve different purposes)?
Eventually, I would like to have all useful features of BPrint in the dynamic buffer AVWriter and deprecate BPrint, since that was written in part to learn and do BPrint better.
OK, sounds good.
Stefano Sabatini (12022-06-17):
I was thinking about mempool (and no, I don't think it's really neeeded for this use case).
I still had to read the implementation, now I think I got what this is about.
You have touched the main difference between AVWriter and BPrint. BPrint is a specific implementation, it works the way it works and return the string as a pointer and a length field. If you are not happy with it, you must take it anyway. AVWriter is a layer of abstraction. A very thin, very lightweight layer. Very ffmpeg. You can make an AVWriter out of anything, as long as it is something where writing to it text makes sense. You just need to write a few low-level callbacks to write bits of string, and benefit from the high-level features of AVWriter. There is already an AVWriter that will let you have a static-or-dynamic buffer allocated with av_realloc(). In fact it is a BPrint inside of it, because no need to duplicate the code. There is already an AVWriter that will use a buffer you already have. There is already an AVWriter that will write the text to the logs without storing it. If you are writing an application that is partially in Rust, you can make an AVWriter that will store the text in a Rust String object. Same goes with Perl, Java, whatever language. If you are writing a GUI application, you can make an AVWriter that will store the text directly in a text widget. You can make an AVWriter that will change the character encoding of the text, or compress it, or encrypt it, and write the result to another AVWriter. Your imagination is the limit. But if you only want good old C strings, you do not need to know any of that, just use one of the built-in AVWriter as shown in the documentation and you are good to go. I have to confess, I am rather proud of the ideas I have had to make an abstraction layer that is lightweight, nimble and future-proof at the same time. Eventually, I would like to go further in that direction, for example with side data types describing themselves how they can be serialized / duplicated / referenced. But later. Regards, -- Nicolas George
On Fri, Jun 17, 2022 at 04:57:55PM +0200, Nicolas George wrote:
Stefano Sabatini (12022-06-17):
I was thinking about mempool (and no, I don't think it's really neeeded for this use case).
I still had to read the implementation, now I think I got what this is about.
You have touched the main difference between AVWriter and BPrint.
BPrint is a specific implementation, it works the way it works and return the string as a pointer and a length field. If you are not happy with it, you must take it anyway.
AVWriter is a layer of abstraction. A very thin, very lightweight layer. Very ffmpeg.
You can make an AVWriter out of anything, as long as it is something where writing to it text makes sense. You just need to write a few low-level callbacks to write bits of string, and benefit from the high-level features of AVWriter.
There is already an AVWriter that will let you have a static-or-dynamic buffer allocated with av_realloc(). In fact it is a BPrint inside of it, because no need to duplicate the code.
There is already an AVWriter that will use a buffer you already have. There is already an AVWriter that will write the text to the logs without storing it.
If you are writing an application that is partially in Rust, you can make an AVWriter that will store the text in a Rust String object. Same goes with Perl, Java, whatever language.
If you are writing a GUI application, you can make an AVWriter that will store the text directly in a text widget.
You can make an AVWriter that will change the character encoding of the text, or compress it, or encrypt it, and write the result to another AVWriter.
Your imagination is the limit. But if you only want good old C strings, you do not need to know any of that, just use one of the built-in AVWriter as shown in the documentation and you are good to go.
I have to confess, I am rather proud of the ideas I have had to make an abstraction layer that is lightweight, nimble and future-proof at the same time. Eventually, I would like to go further in that direction, for example with side data types describing themselves how they can be serialized / duplicated / referenced. But later.
Sorry for my late reply. I do like the AVWriter idea, i think replacing BPrint by it is a good idea I only now looked at your examples you posted in april last year. I may have looked before, i dont remember but from these examples it looks nice Thanks [...] -- Michael GnuPG fingerprint: 9FF2128B147EF6730BADF133611EC787040B0FAB He who knows, does not speak. He who speaks, does not know. -- Lao Tsu
participants (3)
-
Michael Niedermayer -
Nicolas George -
Stefano Sabatini