diff options
author | Takashi Kokubun <takashikkbn@gmail.com> | 2019-06-05 19:28:51 +0900 |
---|---|---|
committer | Takashi Kokubun <takashikkbn@gmail.com> | 2019-06-05 21:07:04 +0900 |
commit | 0a29dc87e62c701db56816cb430daf07a4f02bea (patch) | |
tree | 8c4afbace1e6a42d36e0401d2f8b1ddc7bdea373 | |
parent | f3c877e8deaea91ff27c0fca837c9388d030a896 (diff) |
Optimize CGI.escapeHTML by reducing buffer extension
and switch-case branches.
Buffer allocation optimization using `ALLOCA_N` would be the main
benefit of patch. It eliminates the O(N) buffer extensions.
It also reduces the number of branches using escape table like
https://mattn.kaoriya.net/software/lang/c/20160817011915.htm.
Closes: https://github.com/ruby/ruby/pull/2226
Co-authored-by: Nobuyoshi Nakada <nobu@ruby-lang.org>
Co-authored-by: Yasuhiro MATSUMOTO <mattn.jp@gmail.com>
-rw-r--r-- | benchmark/cgi_escape_html.yml | 40 | ||||
-rw-r--r-- | ext/cgi/escape/escape.c | 85 |
2 files changed, 76 insertions, 49 deletions
diff --git a/benchmark/cgi_escape_html.yml b/benchmark/cgi_escape_html.yml new file mode 100644 index 0000000000..af6abd08ac --- /dev/null +++ b/benchmark/cgi_escape_html.yml @@ -0,0 +1,40 @@ +prelude: require 'cgi/escape' +benchmark: + - name: escape_html_blank + prelude: str = "" + script: CGI.escapeHTML(str) + loop_count: 20000000 + - name: escape_html_short_none + prelude: str = "abcde" + script: CGI.escapeHTML(str) + loop_count: 20000000 + - name: escape_html_short_one + prelude: str = "abcd<" + script: CGI.escapeHTML(str) + loop_count: 20000000 + - name: escape_html_short_all + prelude: str = "'&\"<>" + script: CGI.escapeHTML(str) + loop_count: 5000000 + - name: escape_html_long_none + prelude: str = "abcde" * 300 + script: CGI.escapeHTML(str) + loop_count: 1000000 + - name: escape_html_long_all + prelude: str = "'&\"<>" * 10 + script: CGI.escapeHTML(str) + loop_count: 1000000 + - name: escape_html_real + prelude: | # http://example.com/ + str = <<~HTML + <body> + <div> + <h1>Example Domain</h1> + <p>This domain is established to be used for illustrative examples in documents. You may use this + domain in examples without prior coordination or asking for permission.</p> + <p><a href="http://www.iana.org/domains/example">More information...</a></p> + </div> + </body> + HTML + script: CGI.escapeHTML(str) + loop_count: 1000000 diff --git a/ext/cgi/escape/escape.c b/ext/cgi/escape/escape.c index 78d196db71..76d8f0d067 100644 --- a/ext/cgi/escape/escape.c +++ b/ext/cgi/escape/escape.c @@ -11,27 +11,20 @@ RUBY_EXTERN const signed char ruby_digit36_to_number_table[]; static VALUE rb_cCGI, rb_mUtil, rb_mEscape; static ID id_accept_charset; -static void -html_escaped_cat(VALUE str, char c) -{ - switch (c) { - case '\'': - rb_str_cat_cstr(str, "'"); - break; - case '&': - rb_str_cat_cstr(str, "&"); - break; - case '"': - rb_str_cat_cstr(str, """); - break; - case '<': - rb_str_cat_cstr(str, "<"); - break; - case '>': - rb_str_cat_cstr(str, ">"); - break; - } -} +#define HTML_ESCAPE_MAX_LEN 6 + +static const struct { + uint8_t len; + char str[HTML_ESCAPE_MAX_LEN+1]; +} html_escape_table[UCHAR_MAX+1] = { +#define HTML_ESCAPE(c, str) [c] = {rb_strlen_lit(str), str} + HTML_ESCAPE('\'', "'"), + HTML_ESCAPE('&', "&"), + HTML_ESCAPE('"', """), + HTML_ESCAPE('<', "<"), + HTML_ESCAPE('>', ">"), +#undef HTML_ESCAPE +}; static inline void preserve_original_state(VALUE orig, VALUE dest) @@ -44,40 +37,34 @@ preserve_original_state(VALUE orig, VALUE dest) static VALUE optimized_escape_html(VALUE str) { - long i, len, beg = 0; - VALUE dest = 0; - const char *cstr; - - len = RSTRING_LEN(str); - cstr = RSTRING_PTR(str); - - for (i = 0; i < len; i++) { - switch (cstr[i]) { - case '\'': - case '&': - case '"': - case '<': - case '>': - if (!dest) { - dest = rb_str_buf_new(len); - } - - rb_str_cat(dest, cstr + beg, i - beg); - beg = i + 1; - - html_escaped_cat(dest, cstr[i]); - break; + VALUE vbuf; + char *buf = ALLOCV_N(char, vbuf, RSTRING_LEN(str) * HTML_ESCAPE_MAX_LEN); + const char *cstr = RSTRING_PTR(str); + const char *end = cstr + RSTRING_LEN(str); + + char *dest = buf; + while (cstr < end) { + const unsigned char c = *cstr++; + uint8_t len = html_escape_table[c].len; + if (len) { + memcpy(dest, html_escape_table[c].str, len); + dest += len; + } + else { + *dest++ = c; } } - if (dest) { - rb_str_cat(dest, cstr + beg, len - beg); - preserve_original_state(str, dest); - return dest; + VALUE escaped; + if (RSTRING_LEN(str) < (dest - buf)) { + escaped = rb_str_new(buf, dest - buf); + preserve_original_state(str, escaped); } else { - return rb_str_dup(str); + escaped = rb_str_dup(str); } + ALLOCV_END(vbuf); + return escaped; } static VALUE |