Le decadi 10 germinal, an CCXXV, Steven Liu a écrit : > refer to: http://creativeandcritical.net/str-replace-c > add av_strreplace API for replace string operations. > > Signed-off-by: Steven Liu <l...@chinaffmpeg.org> > --- > libavutil/avstring.c | 77 > ++++++++++++++++++++++++++++++++++++++++++++++++++++ > libavutil/avstring.h | 5 ++++ > 2 files changed, 82 insertions(+) > > diff --git a/libavutil/avstring.c b/libavutil/avstring.c > index 1787a1ef54..52e6e6cd13 100644 > --- a/libavutil/avstring.c > +++ b/libavutil/avstring.c > @@ -231,6 +231,83 @@ int av_strncasecmp(const char *a, const char *b, size_t > n) > return c1 - c2; > } >
> +char *av_strreplace(const char *str, const char *from, const char *to) Before starting: Steven, you seem not completely familiar with the English language. Sometimes, I make complex sentences. If you need, ask and I will rephrase. First, I think the "strreplace" name should have been reserved for the case-sensitive version, using possibly strireplace for the case-insensitive version. Since it was only applied very recently, and is not yet used even in the rest of FFmpeg, I say we fix the name right now. (Note: that does not mean this minute, that means this week, leaving a chance for other to give their opinion. And of course, you yourself are allowed to disagree.) Second, I notice quite a few potential integer overflows. They would need to be fixed if the code is to stay that way. Fortunately, I think we can get rid of all that. Third, and most important for the rest of my discourse: this code is guilty of the sin of premature optimization. It builds a rather complex cache mechanism, but possibly cripples it by introducing an arbitrary limit, and more importantly, it uses av_stristr() repeatedly; av_stristr() itself is not optimized at all, and there are further optimizations for repeated use with the same needle; see the Knuth-Morris-Pratt algorithm. Keep that in mind if anything I write below seems "less efficient" than what is already there. I told that it duplicated the BPrint API, I was wrong: the API it duplicates dynarray. But it could have been BPrint, and they are the same anyway. Because there are three obvious ways of implementing this function. 1. (the present implementation) Find and keep track of all occurrences of the needle, allocate the target string with the exact size, and build it. 2. Find and count all occurrences of the needle, allocate the target string with the exact size, and then re-find all occurrences of the needle to build it. 3. Build the target string on the fly. (Lexicon: strstr(haystack, needle): to find a needle in a haystack.) By itself, the simplest solution to implement in C is #2 because it uses only a single dynamic allocation and very simple loops. It requires twice as much needle searches, but if they are optimized it may well be a non-issue. Solution #1 addresses the "twice as much needle searches" issue by storing the results. I believe it is a very bad idea, because storing the results requires a rather complex dynamic reallocation mechanism: in this code, the dynamic array to store the results is about one third of the whole code, and most of the integer overflows. One of the av_dynarray API can make it simpler, but it is still a little tricky to use. And I suspect it is not even worth the effort; only a benchmark could tell: premature optimization. I say: let us NOT use solution #1. Solution #3 takes a completely different road. To implement it in plain C would require the same kind of tricky dynamic reallocation as solution #1, for the target string instead of the positions. But we have the BPrint API that makes it very simple. My advice would be: go with solution #3, and if performance is a problem start by implementing KMP. If per chance you prefer solution #2, you can achieve it by removing all the "cache"-related code and replacing access to the "cache" with a new call to av_stristr(). > +{ > + /* Adjust each of the below values to suit your needs. */ > + /* Increment positions cache size initially by this number. */ > + size_t cache_sz_inc = 16; > + /* Thereafter, each time capacity needs to be increased, > + * multiply the increment by this factor. */ > + const size_t cache_sz_inc_factor = 3; > + /* But never increment capacity by more than this number. */ > + const size_t cache_sz_inc_max = 1048576; > + uintptr_t *pos_cache_tmp, *pos_cache = NULL; > + size_t cache_sz = 0; > + /* Increase the cache size when necessary. */ > + if (cache_sz < count) { > + cache_sz += cache_sz_inc; > + pos_cache_tmp = av_realloc(pos_cache, sizeof(*pos_cache) * > cache_sz); > + if (!pos_cache_tmp) { > + goto end_strreplace; > + } else pos_cache = pos_cache_tmp; > + cache_sz_inc *= cache_sz_inc_factor; > + if (cache_sz_inc > cache_sz_inc_max) { > + cache_sz_inc = cache_sz_inc_max; > + } > + } > + pos_cache[count-1] = pstr2 - str; > + pstr = pstr2 + fromlen; All this is the code needed to keep track of the positions of the needle. One way or another, it must go away. > + retlen = orglen + (tolen - fromlen) * count; This is a potential integer overflow, for example. > + /* Otherwise, duplicate the string whilst performing > + * the replacements using the position cache. */ > + pret = ret; > + memcpy(pret, str, pos_cache[0]); > + pret += pos_cache[0]; > + for (i = 0; i < count; i++) { > + memcpy(pret, to, tolen); > + pret += tolen; > + pstr = str + pos_cache[i] + fromlen; > + cpylen = (i == count-1 ? orglen : pos_cache[i+1]) - pos_cache[i] > - fromlen; > + memcpy(pret, pstr, cpylen); > + pret += cpylen; > + } If there are n copies of the needle, there are n+1 pieces of string around them, and one of them has to be taken out of the loop. This version isolates the one before the first needle. It would have been much simpler to isolate the one after the last needle. The formula for cpylen, in particular, is a symptom of this non-optimal choice. The loop could look that way: while (needle) { copy from pos to needle update pos add replacement string } copy from pos to the end The BPrint version is actually exactly the same: av_bprint_init() while (needle) { av_bprint_append_data() to copy from pos to needle update pos av_bprint_append_data() to add the replacement string } av_bprint_append_data() to copy from pos to the end av_bprint_is_complete() to check malloc errors av_print_finalize() All in all, I think the BPrint version is the easiest to implement right now. Regards, -- Nicolas George
signature.asc
Description: Digital signature
_______________________________________________ ffmpeg-devel mailing list ffmpeg-devel@ffmpeg.org http://ffmpeg.org/mailman/listinfo/ffmpeg-devel