eina_str speedups.
authorbarbieri <barbieri@7cbeb6ba-43b4-40fd-8cce-4c39aea84d33>
Sat, 27 Feb 2010 03:42:27 +0000 (03:42 +0000)
committerbarbieri <barbieri@7cbeb6ba-43b4-40fd-8cce-4c39aea84d33>
Sat, 27 Feb 2010 03:42:27 +0000 (03:42 +0000)
 * eina_str_split() now does the minimum number of passes and
   allocations. The first pass figures out the string size (strlen())
   and number of delimiters, so we can allocate the exact number of
   elements in array. The second repeats the loop copying elements to
   string and also setting them to the result array.

 * eina_str_split_full() is a variation of eina_str_split() that
   returns also the number of elements in array, in the case you need
   to pre-allocate another array to copy.

 * eina_strlen_bounded() is introduced to limit strlen() results, this
   is used in has_prefix and has_suffix, but possibly other use cases
   where string must be of a maximum size as we don't do useless
   iterations;

git-svn-id: svn+ssh://svn.enlightenment.org/var/svn/e/trunk/eina@46547 7cbeb6ba-43b4-40fd-8cce-4c39aea84d33

src/include/Makefile.am
src/include/eina_inline_str.x [new file with mode: 0644]
src/include/eina_str.h
src/lib/eina_str.c
src/tests/Makefile.am
src/tests/eina_suite.c
src/tests/eina_suite.h
src/tests/eina_test_str.c [new file with mode: 0644]

index 9730adc..71b9a77 100644 (file)
@@ -44,6 +44,7 @@ eina_hamster.h \
 eina_matrixsparse.h \
 eina_inline_tiler.x \
 eina_str.h \
+eina_inline_str.x \
 eina_strbuf.h
 
 installed_mainheaderdir = $(includedir)/eina-@VMAJ@
diff --git a/src/include/eina_inline_str.x b/src/include/eina_inline_str.x
new file mode 100644 (file)
index 0000000..bcfdb5c
--- /dev/null
@@ -0,0 +1,52 @@
+/* EINA - EFL data type library
+ * Copyright (C) 2002-2008 Gustavo Sverzut Barbieri
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library;
+ * if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef EINA_STR_INLINE_H_
+#define EINA_STR_INLINE_H_
+
+/**
+ * @addtogroup Eina_Str_Group Str
+ *
+ * @{
+ */
+
+/**
+ * strlen() that will count up to maxlen bytes.
+ *
+ * If one wants to know the size of @a str, but it should not be
+ * greater than @a maxlen, then use this function and avoid needless
+ * iterations after that size.
+ *
+ * @param str the string pointer, must be valid and not @c NULL.
+ * @param maxlen the maximum length to allow.
+ * @return the string size or (size_t)-1 if greater than @a maxlen.
+ */
+static inline size_t
+eina_strlen_bounded(const char *str, size_t maxlen)
+{
+   const char *itr, *str_maxend = str + maxlen;
+   for (itr = str; *itr != '\0'; itr++)
+     if (itr == str_maxend) return (size_t)-1;
+   return itr - str;
+}
+
+/**
+ * @}
+ */
+
+#endif /* EINA_STR_INLINE_H_ */
index c0b76ef..3f977e2 100644 (file)
  */
 
 /* strlcpy implementation for libc's lacking it */
-EAPI size_t eina_strlcpy(char *dst, const char *src, size_t siz);
-EAPI size_t eina_strlcat(char *dst, const char *src, size_t siz);
+EAPI size_t eina_strlcpy(char *dst, const char *src, size_t siz) EINA_ARG_NONNULL(1, 2);
+EAPI size_t eina_strlcat(char *dst, const char *src, size_t siz) EINA_ARG_NONNULL(1, 2);
 
-EAPI Eina_Bool eina_str_has_prefix(const char *str, const char *prefix);
+EAPI Eina_Bool eina_str_has_prefix(const char *str, const char *prefix) EINA_PURE EINA_ARG_NONNULL(1, 2);
 
-EAPI Eina_Bool eina_str_has_suffix(const char *str, const char *suffix);
-EAPI Eina_Bool eina_str_has_extension(const char *str, const char *ext);
+EAPI Eina_Bool eina_str_has_suffix(const char *str, const char *suffix) EINA_PURE EINA_ARG_NONNULL(1, 2);
+EAPI Eina_Bool eina_str_has_extension(const char *str, const char *ext) EINA_PURE EINA_ARG_NONNULL(1, 2);
 
-EAPI char **eina_str_split(const char *string, const char *delimiter,
-                            int max_tokens);
+EAPI char **eina_str_split(const char *string, const char *delimiter, int max_tokens) EINA_ARG_NONNULL(1, 2);
+EAPI char **eina_str_split_full(const char *string, const char *delimiter, int max_tokens, unsigned int *elements) EINA_ARG_NONNULL(1, 2, 3);
 
-EAPI size_t eina_str_join_len(char *dst, size_t size, char sep, const char *a, size_t a_len, const char *b, size_t b_len);
+EAPI size_t eina_str_join_len(char *dst, size_t size, char sep, const char *a, size_t a_len, const char *b, size_t b_len) EINA_ARG_NONNULL(1, 4, 6);
 
-EAPI char *eina_str_convert(const char *enc_from, const char *enc_to, const char *text);
+EAPI char *eina_str_convert(const char *enc_from, const char *enc_to, const char *text) EINA_WARN_UNUSED_RESULT EINA_MALLOC EINA_ARG_NONNULL(1, 2, 3);
 
-EAPI char *eina_str_escape(const char *str);
+EAPI char *eina_str_escape(const char *str) EINA_WARN_UNUSED_RESULT EINA_MALLOC EINA_ARG_NONNULL(1);
 
 
+static inline size_t eina_str_join(char *dst, size_t size, char sep, const char *a, const char *b) EINA_ARG_NONNULL(1, 4, 5);
+
 /**
  * @brief Join two strings of known length.
  *
@@ -69,4 +71,8 @@ static inline size_t eina_str_join(char *dst, size_t size, char sep, const char
  */
 #define eina_str_join_static(dst, sep, a, b) eina_str_join_len(dst, sizeof(dst), sep, a, (sizeof(a) > 0) ? sizeof(a) - 1 : 0, b, (sizeof(b) > 0) ? sizeof(b) - 1 : 0)
 
+static inline size_t eina_strlen_bounded(const char *str, size_t maxlen) EINA_PURE EINA_WARN_UNUSED_RESULT EINA_ARG_NONNULL(1);
+
+#include "eina_inline_str.x"
+
 #endif /* EINA_STR_H */
index 117d14a..6127994 100644 (file)
@@ -28,6 +28,7 @@
 # include "config.h"
 #endif
 
+#include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <limits.h>
@@ -52,7 +53,7 @@
  * Internal helper function used by eina_str_has_suffix() and
  * eina_str_has_extension()
  */
-static Eina_Bool
+static inline Eina_Bool
 eina_str_has_suffix_helper(const char *str,
                           const char *suffix,
                           int (*cmp)(const char *, const char *))
@@ -61,13 +62,102 @@ eina_str_has_suffix_helper(const char *str,
    size_t suffix_len;
 
    str_len = strlen(str);
-   suffix_len = strlen(suffix);
-   if (suffix_len > str_len)
+   suffix_len = eina_strlen_bounded(suffix, str_len);
+   if (suffix_len == (size_t)-1)
      return EINA_FALSE;
 
    return cmp(str + str_len - suffix_len, suffix) == 0;
 }
 
+static inline char **
+eina_str_split_full_helper(const char *str, const char *delim, int max_tokens, unsigned int *elements)
+{
+   char *s, **str_array;
+   const char *src;
+   size_t len, dlen;
+   unsigned int tokens;
+
+   dlen = strlen(delim);
+   if (dlen == 0)
+     {
+       if (elements) *elements = 0;
+       return NULL;
+     }
+
+   tokens = 0;
+   src = str;
+   /* count tokens and check strlen(str) */
+   while (*src != '\0')
+     {
+       const char *d = delim, *d_end = d + dlen;
+       const char *tmp = src;
+       for (; (d < d_end) && (*tmp != '\0'); d++, tmp++)
+         {
+            if (EINA_LIKELY(*d != *tmp))
+              break;
+         }
+       if (EINA_UNLIKELY(d == d_end))
+         {
+            src = tmp;
+            tokens++;
+         }
+       else
+         src++;
+     }
+   len = src - str;
+
+   if ((max_tokens > 0) && (tokens > (unsigned int)max_tokens))
+     tokens = max_tokens;
+
+   str_array = malloc(sizeof(char *) * (tokens + 2));
+   if (!str_array)
+     {
+       if (elements) *elements = 0;
+       return NULL;
+     }
+
+   s = malloc(len + 1);
+   if (!s)
+     {
+       free(str_array);
+       if (elements) *elements = 0;
+       return NULL;
+     }
+
+   /* copy tokens and string */
+   tokens = 0;
+   str_array[0] = s;
+   src = str;
+   while (*src != '\0')
+     {
+       const char *d = delim, *d_end = d + dlen;
+       const char *tmp = src;
+       for (; (d < d_end) && (*tmp != '\0'); d++, tmp++)
+         {
+            if (EINA_LIKELY(*d != *tmp))
+              break;
+         }
+       if (EINA_UNLIKELY(d == d_end))
+         {
+            src = tmp;
+            *s = '\0';
+            s += dlen;
+            tokens++;
+            str_array[tokens] = s;
+         }
+       else
+         {
+            *s = *src;
+            s++;
+            src++;
+         }
+     }
+   *s = '\0';
+   str_array[tokens + 1] = NULL;
+   if (elements) *elements = (tokens + 1);
+   return str_array;
+}
+
 /**
  * @endcond
  */
@@ -189,8 +279,8 @@ eina_str_has_prefix(const char *str, const char *prefix)
    size_t prefix_len;
 
    str_len = strlen(str);
-   prefix_len = strlen(prefix);
-   if (prefix_len > str_len)
+   prefix_len = eina_strlen_bounded(prefix, str_len);
+   if (prefix_len == (size_t)-1)
      return EINA_FALSE;
 
    return (strncmp(str, prefix, prefix_len) == 0);
@@ -236,11 +326,13 @@ eina_str_has_extension(const char *str, const char *ext)
 }
 
 /**
- * @brief Split a string using a delimiter.
+ * @brief Split a string using a delimiter and returns number of elements.
  *
  * @param str The string to split.
  * @param delim The string which specifies the places at which to split the string.
  * @param max_tokens The maximum number of strings to split string into.
+ * @param elements Where to return the number of elements in returned
+ *        array (not counting the terminating @c NULL). May be @c NULL.
  * @return A newly-allocated NULL-terminated array of strings.
  *
  * This functin splits @p str into a maximum of @p max_tokens pieces,
@@ -251,34 +343,37 @@ eina_str_has_extension(const char *str, const char *ext)
  * array contains the remainder of string. The returned value is a
  * newly allocated NUL-terminated array of string. To free it, free
  * the first element of the array and the array itself.
+ *
+ * @see eina_str_split()
  */
 EAPI char **
-eina_str_split(const char *str, const char *delim, int max_tokens)
+eina_str_split_full(const char *str, const char *delim, int max_tokens, unsigned int *elements)
 {
-   char *s, *sep, **str_array;
-   size_t len, dlen;
-   int i;
-
-   if (*delim == '\0')
-     return NULL;
-
-   max_tokens = ((max_tokens <= 0) ? (INT_MAX) : (max_tokens - 1));
-   len = strlen(str);
-   dlen = strlen(delim);
-   s = strdup(str);
-   str_array = malloc(sizeof(char *) * (len + 1));
-   for (i = 0; (i < max_tokens) && (sep = strstr(s, delim)); i++)
-     {
-       str_array[i] = s;
-       s = sep + dlen;
-       *sep = 0;
-     }
+   return eina_str_split_full_helper(str, delim, max_tokens, elements);
+}
 
-   str_array[i++] = s;
-   str_array = realloc(str_array, sizeof(char *) * (i + 1));
-   str_array[i] = NULL;
 
-   return str_array;
+/**
+ * @brief Split a string using a delimiter.
+ *
+ * @param str The string to split.
+ * @param delim The string which specifies the places at which to split the string.
+ * @param max_tokens The maximum number of strings to split string into.
+ * @return A newly-allocated NULL-terminated array of strings.
+ *
+ * This functin splits @p str into a maximum of @p max_tokens pieces,
+ * using the given delimiter @p delim. @p delim is not included in any
+ * of the resulting strings, unless @p max_tokens is reached. If
+ * @p max_tokens is less than @c 1, the string is splitted completely. If
+ * @p max_tokens is reached, the last string in the returned string
+ * array contains the remainder of string. The returned value is a
+ * newly allocated NUL-terminated array of string. To free it, free
+ * the first element of the array and the array itself.
+ */
+EAPI char **
+eina_str_split(const char *str, const char *delim, int max_tokens)
+{
+   return eina_str_split_full_helper(str, delim, max_tokens, NULL);
 }
 
 /**
index 842d286..1b0736f 100644 (file)
@@ -54,7 +54,8 @@ eina_test_rectangle.c \
 eina_test_list.c       \
 eina_test_matrixsparse.c \
 eina_test_tiler.c       \
-eina_test_strbuf.c
+eina_test_strbuf.c     \
+eina_test_str.c
 
 eina_suite_LDADD = @CHECK_LIBS@ $(top_builddir)/src/lib/libeina.la
 
index faa8339..c009771 100644 (file)
@@ -56,6 +56,7 @@ static const Eina_Test_Case etc[] = {
   { "Matrix Sparse", eina_test_matrixsparse },
   { "Eina Tiler", eina_test_tiler },
   { "Eina Strbuf", eina_test_strbuf },
+  { "String", eina_test_str },
   { NULL, NULL }
 };
 
index 92b3969..3b1b884 100644 (file)
@@ -44,5 +44,6 @@ void eina_test_rectangle(TCase *tc);
 void eina_test_matrixsparse(TCase *tc);
 void eina_test_tiler(TCase *tc);
 void eina_test_strbuf(TCase *tc);
+void eina_test_str(TCase *tc);
 
 #endif /* EINA_SUITE_H_ */
diff --git a/src/tests/eina_test_str.c b/src/tests/eina_test_str.c
new file mode 100644 (file)
index 0000000..ad99326
--- /dev/null
@@ -0,0 +1,181 @@
+/* EINA - EFL data type library
+ * Copyright (C) 2010 Gustavo Sverzut Barbieri
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library;
+ * if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <stdio.h>
+
+#include "eina_suite.h"
+#include "Eina.h"
+
+START_TEST(str_simple)
+{
+   eina_init();
+
+   fail_if(!eina_str_has_prefix("", ""));
+
+   fail_if(!eina_str_has_prefix("x", "x"));
+   fail_if(!eina_str_has_prefix("xab", "x"));
+   fail_if(!eina_str_has_prefix("xab", "xab"));
+
+   fail_if(eina_str_has_prefix("x", "xab"));
+   fail_if(eina_str_has_prefix("xab", "xyz"));
+   fail_if(eina_str_has_prefix("", "x"));
+   fail_if(eina_str_has_prefix("X", "x"));
+   fail_if(eina_str_has_prefix("xAb", "X"));
+   fail_if(eina_str_has_prefix("xAb", "xab"));
+
+
+   fail_if(!eina_str_has_suffix("", ""));
+
+   fail_if(!eina_str_has_suffix("x", "x"));
+   fail_if(!eina_str_has_suffix("abx", "x"));
+   fail_if(!eina_str_has_suffix("xab", "xab"));
+
+   fail_if(eina_str_has_suffix("x", "xab"));
+   fail_if(eina_str_has_suffix("xab", "xyz"));
+   fail_if(eina_str_has_suffix("", "x"));
+   fail_if(eina_str_has_suffix("X", "x"));
+   fail_if(eina_str_has_suffix("aBx", "X"));
+   fail_if(eina_str_has_suffix("xaB", "Xab"));
+
+
+   fail_if(!eina_str_has_extension("", ""));
+
+   fail_if(!eina_str_has_extension("x", "x"));
+   fail_if(!eina_str_has_extension("abx", "x"));
+   fail_if(!eina_str_has_extension("xab", "xab"));
+   fail_if(!eina_str_has_extension("x", "X"));
+   fail_if(!eina_str_has_extension("abx", "X"));
+   fail_if(!eina_str_has_extension("xab", "Xab"));
+   fail_if(!eina_str_has_extension("X", "X"));
+   fail_if(!eina_str_has_extension("aBx", "X"));
+   fail_if(!eina_str_has_extension("xaB", "Xab"));
+
+   fail_if(eina_str_has_extension("x", "xab"));
+   fail_if(eina_str_has_extension("xab", "xyz"));
+   fail_if(eina_str_has_extension("", "x"));
+   fail_if(eina_str_has_extension("x", "xAb"));
+   fail_if(eina_str_has_extension("xab", "xYz"));
+   fail_if(eina_str_has_extension("", "x"));
+
+   fail_if(eina_strlen_bounded("abc", 1024) != strlen("abc"));
+   fail_if(eina_strlen_bounded("abc", 2) != (size_t)-1);
+
+   eina_shutdown();
+}
+END_TEST
+
+START_TEST(str_split)
+{
+   char **result;
+   unsigned int elements;
+
+   eina_init();
+
+   result = eina_str_split_full("nomatch", "", -1, &elements);
+   fail_if(result != NULL);
+   fail_if(elements != 0);
+
+   result = eina_str_split_full("nomatch", "x", -1, &elements);
+   fail_if(result == NULL);
+   fail_if(elements != 1);
+   fail_if(strcmp(result[0], "nomatch") != 0);
+   free(result[0]);
+   free(result);
+
+   result = eina_str_split_full("nomatch", "xyz", -1, &elements);
+   fail_if(result == NULL);
+   fail_if(elements != 1);
+   fail_if(strcmp(result[0], "nomatch") != 0);
+   free(result[0]);
+   free(result);
+
+   result = eina_str_split_full("match:match:match", ":", -1, &elements);
+   fail_if(result == NULL);
+   fail_if(elements != 3);
+   while (elements >= 1)
+     {
+       elements--;
+       fail_if(strcmp(result[elements], "match") != 0);
+     }
+   free(result[0]);
+   free(result);
+
+   result = eina_str_split_full("a:b:c", ":", -1, &elements);
+   fail_if(result == NULL);
+   fail_if(elements != 3);
+   fail_if(strcmp(result[0], "a") != 0);
+   fail_if(strcmp(result[1], "b") != 0);
+   fail_if(strcmp(result[2], "c") != 0);
+   free(result[0]);
+   free(result);
+
+   result = eina_str_split_full("a:b:", ":", -1, &elements);
+   fail_if(result == NULL);
+   fail_if(elements != 3);
+   fail_if(strcmp(result[0], "a") != 0);
+   fail_if(strcmp(result[1], "b") != 0);
+   fail_if(strcmp(result[2], "") != 0);
+   free(result[0]);
+   free(result);
+
+   result = eina_str_split_full(":b:c", ":", -1, &elements);
+   fail_if(result == NULL);
+   fail_if(elements != 3);
+   fail_if(strcmp(result[0], "") != 0);
+   fail_if(strcmp(result[1], "b") != 0);
+   fail_if(strcmp(result[2], "c") != 0);
+   free(result[0]);
+   free(result);
+
+   result = eina_str_split_full(":", ":", -1, &elements);
+   fail_if(result == NULL);
+   fail_if(elements != 2);
+   fail_if(strcmp(result[0], "") != 0);
+   fail_if(strcmp(result[1], "") != 0);
+   free(result[0]);
+   free(result);
+
+   result = eina_str_split_full("a", "!!!!!!!!!", -1, &elements);
+   fail_if(result == NULL);
+   fail_if(elements != 1);
+   fail_if(strcmp(result[0], "a") != 0);
+   free(result[0]);
+   free(result);
+
+   result = eina_str_split_full("aaba", "ab", -1, &elements);
+   fail_if(result == NULL);
+   fail_if(elements != 2);
+   fail_if(strcmp(result[0], "a") != 0);
+   fail_if(strcmp(result[1], "a") != 0);
+   free(result[0]);
+   free(result);
+
+   eina_shutdown();
+}
+END_TEST
+
+void
+eina_test_str(TCase *tc)
+{
+   tcase_add_test(tc, str_simple);
+   tcase_add_test(tc, str_split);
+}