gstrfuncs: Add inline version of g_strdup()

g_strdup() is often used to duplicate static strings, in these cases the
compiler could use a faster path because it knows the length of the
string at compile time, but this cannot happen because our g_strdup()
implementation is hidden.

To improve this case, we add a simple implementation of g_strdup() when
it is used with static or NULL strings that explicitly uses strlen,
g_malloc and memcpy to give hints to the compiler how to behave better.

This has definitely some benefits in terms of performances, causing an
iteration of 1000000 string duplication to drop from 2.7002s to 1.9428s
for a static string and from ~0.6584s to ~0.4408 for a NULL one.

Since compiler can optimize these cases quite a bit, the generated code
[2] is not increasing a lot, given that it can now avoid generating some
code or do it in few simpler steps.

Update tests to cover both inlined and non inlined cases.

[1] https://gitlab.gnome.org/GNOME/glib/-/merge_requests/3209#note_1644383
[2] https://gitlab.gnome.org/GNOME/glib/-/merge_requests/3209#note_1646662
This commit is contained in:
Marco Trevisan (Treviño) 2023-01-17 16:37:28 +01:00
parent ed42f57704
commit cc0fb5e77c
4 changed files with 93 additions and 5 deletions

View File

@ -352,12 +352,12 @@ get_C_locale (void)
* Returns: a newly-allocated copy of @str
*/
gchar*
g_strdup (const gchar *str)
(g_strdup) (const gchar *str)
{
gchar *new_str;
gsize length;
if (str)
if G_LIKELY (str)
{
length = strlen (str) + 1;
new_str = g_new (char, length);

View File

@ -153,7 +153,7 @@ gboolean (g_str_has_prefix) (const gchar *str,
* Without it, it thinks strlen and memcmp may be getting passed NULL
* despite the explicit check for NULL right above the calls.
*/
#define _G_STR_NONNULL(x) (x + !x)
#define _G_STR_NONNULL(x) ((x) + !(x))
#define g_str_has_prefix(STR, PREFIX) \
(__builtin_constant_p (PREFIX)? \
@ -203,6 +203,23 @@ gboolean (g_str_has_prefix) (const gchar *str,
(g_str_has_suffix) (STR, SUFFIX) \
)
#define g_strdup(STR) \
(__builtin_constant_p ((STR)) ? \
(G_LIKELY ((STR) != NULL) ? \
G_GNUC_EXTENSION ({ \
const char *const ___str = ((STR)); \
const char *const __str = _G_STR_NONNULL (___str); \
const size_t __str_len = strlen (__str) + 1; \
char *__dup_str = (char *) g_malloc (__str_len); \
(char *) memcpy (__dup_str, __str, __str_len); \
}) \
: \
(char *) (NULL) \
) \
: \
(g_strdup) ((STR)) \
)
#endif /* !defined (__GTK_DOC_IGNORE__) && !defined (__GI_SCANNER__) */
#endif /* G_GNUC_CHECK_VERSION (2, 0) */
@ -279,7 +296,7 @@ gchar* g_strup (gchar *string);
* ought to be freed with g_free from the caller at some point.
*/
GLIB_AVAILABLE_IN_ALL
gchar* g_strdup (const gchar *str) G_GNUC_MALLOC;
gchar* (g_strdup) (const gchar *str) G_GNUC_MALLOC;
GLIB_AVAILABLE_IN_ALL
gchar* g_strdup_printf (const gchar *format,
...) G_GNUC_PRINTF (1, 2) G_GNUC_MALLOC;

View File

@ -304,6 +304,7 @@ test_str_equal (void)
{
const char *str_a = "a";
char *str_b = g_strdup ("b");
char *str_null = g_strdup (NULL);
gconstpointer str_a_ptr = str_a, str_b_ptr = str_b;
const unsigned char *str_c = (const unsigned char *) "c";
@ -317,10 +318,36 @@ test_str_equal (void)
g_assert_true (g_str_equal (str_a, str_a_ptr));
g_assert_false (g_str_equal (str_a_ptr, str_b_ptr));
g_assert_false (g_str_equal (str_c, str_b));
g_assert_cmpstr (str_b, !=, str_null);
g_free (str_b);
}
static void
test_strdup (void)
{
gchar *str;
g_assert_null ((g_strdup) (NULL));
str = (g_strdup) ("C++ is cool too!");
g_assert_nonnull (str);
g_assert_cmpstr (str, ==, "C++ is cool too!");
g_free (str);
}
static void
test_strdup_macro (void)
{
gchar *str;
g_assert_null (g_strdup (NULL));
str = g_strdup ("C++ is cool too!");
g_assert_nonnull (str);
g_assert_cmpstr (str, ==, "C++ is cool too!");
g_free (str);
}
static void
test_string_append (void)
@ -444,6 +471,8 @@ main (int argc, char *argv[])
g_test_add_func ("/C++/clear-pointer", test_clear_pointer);
g_test_add_func ("/C++/steal-pointer", test_steal_pointer);
g_test_add_func ("/C++/str-equal", test_str_equal);
g_test_add_func ("/C++/strdup", test_strdup);
g_test_add_func ("/C++/strdup/macro", test_strdup_macro);
g_test_add_func ("/C++/string-append", test_string_append);
return g_test_run ();

View File

@ -499,12 +499,53 @@ test_strdup (void)
{
gchar *str;
g_assert_null ((g_strdup) (NULL));
str = (g_strdup) (GLIB_TEST_STRING);
g_assert_nonnull (str);
g_assert_cmpstr (str, ==, GLIB_TEST_STRING);
char *other_str = (g_strdup) (str);
g_free (str);
g_assert_nonnull (other_str);
g_assert_cmpstr (other_str, ==, GLIB_TEST_STRING);
g_clear_pointer (&other_str, g_free);
str = (g_strdup) ("");
g_assert_cmpint (str[0], ==, '\0');
g_assert_cmpstr (str, ==, "");
g_clear_pointer (&str, g_free);
}
static void
test_strdup_inline (void)
{
gchar *str;
#if G_GNUC_CHECK_VERSION (2, 0)
#ifndef g_strdup
#error g_strdup() should be defined as a macro in this platform!
#endif
#else
g_test_incomplete ("g_strdup() is not inlined in this platform");
#endif
/* Testing inline version of g_strdup() function with various positive and
* negative cases */
g_assert_null (g_strdup (NULL));
str = g_strdup (GLIB_TEST_STRING);
g_assert_nonnull (str);
g_assert_cmpstr (str, ==, GLIB_TEST_STRING);
g_free (str);
char *other_str = g_strdup (str);
g_clear_pointer (&str, g_free);
g_assert_nonnull (other_str);
g_assert_cmpstr (other_str, ==, GLIB_TEST_STRING);
g_clear_pointer (&other_str, g_free);
str = g_strdup ("");
g_assert_cmpint (str[0], ==, '\0');
@ -2698,6 +2739,7 @@ main (int argc,
g_test_add_func ("/strfuncs/strconcat", test_strconcat);
g_test_add_func ("/strfuncs/strdelimit", test_strdelimit);
g_test_add_func ("/strfuncs/strdup", test_strdup);
g_test_add_func ("/strfuncs/strdup/inline", test_strdup_inline);
g_test_add_func ("/strfuncs/strdup-printf", test_strdup_printf);
g_test_add_func ("/strfuncs/strdupv", test_strdupv);
g_test_add_func ("/strfuncs/strerror", test_strerror);