什么是使用C替换更高性能的最佳方法?

时间:2017-07-03 21:43:50

标签: c performance replace str-replace

我想用utf8支持更好的性能来编写好的功能。

我做了深入研究,发现了以下Replace()候选人:

char* replace(char* orig, char* rep, char* with)
{
  //33-34
    char* result; // the return string
    char* ins;    // the next insert point
    char* tmp;    // varies
    size_t len_rep;  // length of rep
    size_t len_with; // length of with
    size_t len_front; // distance between rep and end of last rep
    int count;    // number of replacements
    /* char* strstr(char const* s1, char const* s2);
    retourne un pointeur vers la première occurrence de s2 dans s1
    (ou NULL si s2 n’est pas incluse dans s1). */
    if (!orig)
        return NULL;
    if (!rep || !(len_rep = strlen(rep)))
        return NULL;
    if (!(ins = strstr(orig, rep)))
        return NULL;
    if (!with)
        with = "";
    len_with = strlen(with);
    /*  {   initialisation;
            while (condition) {
                Instructions
                mise_à_jour;
        } }*/
    // compte le nombre d'occurences de la chaîne à remplacer
    for (count = 0; (tmp = strstr(ins, rep)); ++count) {
        ins = tmp + len_rep;
    }
    // allocation de mémoire pour la nouvelle chaîne
    tmp = result = malloc(strlen(orig) + (len_with - len_rep) * count + 1);
    if (!result)
        return NULL;
    /* char* strcpy(char* dest, char const* src);
    copie la chaîne src dans la chaîne dest. Retourne dest.
    Attention ! aucune vérification de taille n’est effectuée ! */

    /* char* strncpy(char* dest, char const* src, size_t n);
    copie les n premiers caractères de src dans dest. Retourne dest.
    Attention ! n’ajoute pas le '\0' à la fin si src contient plus de n
    caractères !*/
    // from here on,
    //    tmp points to the end of the result string
    //    ins points to the next occurrence of rep in orig
    //    orig points to the remainder of orig after "end of rep"
    while (count--) { // count évaluée, puis incrémentée
    // donc ici tant que count est > 0
        ins = strstr(orig, rep);
        len_front = ins - orig;
        tmp = strncpy(tmp, orig, len_front) + len_front;
        tmp = strcpy(tmp, with) + len_with;
        orig += len_front + len_rep; // move to next "end of rep"
    }
    strcpy(tmp, orig);
    return result;
}


char* replace8(char *str, char *old,char *new)
{
  //1.2 :||||||||||
  int i, count = 0;
  int newlen = strlen(new);
  int oldlen = strlen(old);
  for (i = 0; str[i]; ++i)
    if (strstr(&str[i], old) == &str[i])
      ++count, i += oldlen - 1;
  char *ret = (char *) calloc(i + 1 + count * (newlen - oldlen), sizeof(char));
  if (!ret) return "";
  i = 0;
  while (*str)
    if (strstr(str, old) == str)
      strcpy(&ret[i], new),
      i += newlen,
      str += oldlen;
    else
      ret[i++] = *str++;
  ret[i] = '\0';
  return ret;
}



char *replace7(char *orig, char *rep, char *with)
{
  //33-34-35
    char *result; // the return string
    char *ins;    // the next insert point
    char *tmp;    // varies
    int len_rep;  // length of rep
    int len_with; // length of with
    int len_front; // distance between rep and end of last rep
    int count;    // number of replacements
    if (!orig)
    {
        return NULL;
    }
    if (!rep)
    {
        rep = "";
    }
    len_rep = strlen(rep);
    if (!with)
    {
        with = "";
    }
    len_with = strlen(with);

    ins = orig;
    for (count = 0; tmp = strstr(ins, rep); ++count)
    {
        ins = tmp + len_rep;
    }
    // first time through the loop, all the variable are set correctly
    // from here on,
    //    tmp points to the end of the result string
    //    ins points to the next occurrence of rep in orig
    //    orig points to the remainder of orig after "end of rep"
    tmp = result = malloc(strlen(orig) + (len_with - len_rep) * count + 1);
    if (!result)
    {
        return NULL;
    }
    while (count--)
    {
        ins = strstr(orig, rep);
        len_front = ins - orig;
        tmp = strncpy(tmp, orig, len_front) + len_front;
        tmp = strcpy(tmp, with) + len_with;
        orig += len_front + len_rep; // move to next "end of rep"
    }
    strcpy(tmp, orig);
    return result;
}



char *replace6(char *st, char *orig, char *repl)
{
  //17-18
  static char buffer[4000];
  char *ch;
  if (!(ch = strstr(st, orig)))
   return st;
  strncpy(buffer, st, ch-st);
  buffer[ch-st] = 0;
  sprintf(buffer+(ch-st), "%s%s", repl, ch+strlen(orig));
  return buffer;
}



char* replace3(char* s, char* term, char* new_term)
{
  //error
    char *nw = NULL, *pos;
    char *cur = s;
    while(pos = strstr(cur, term))
    {
        nw = (char*)realloc(nw, pos - cur + strlen(new_term));
        strncat(nw, cur, pos-cur);
        strcat(nw, new_term);
        cur = pos + strlen(term);
    }
    strcat(nw, cur);
    free(s);
    return nw;
}



char *replace2(char *original,char *pattern,char *replacement)
{
  //34-37
  size_t replen = strlen(replacement);
  size_t patlen = strlen(pattern);
  size_t orilen = strlen(original);
  size_t patcnt = 0;
  char * oriptr;
  char * patloc;
  // find how many times the pattern occurs in the original string
  for (oriptr = original; patloc = strstr(oriptr, pattern); oriptr = patloc + patlen)
  {
    patcnt++;
  }
  {
    // allocate memory for the new string
    size_t retlen = orilen + patcnt * (replen - patlen);
    char * const returned = (char *) malloc( sizeof(char) * (retlen + 1) );
    if (returned != NULL)
    {
      // copy the original string,
      // replacing all the instances of the pattern
      char * retptr = returned;
      for (oriptr = original; patloc = strstr(oriptr, pattern); oriptr = patloc + patlen)
      {
        size_t skplen = patloc - oriptr;
        // copy the section until the occurence of the pattern
        strncpy(retptr, oriptr, skplen);
        retptr += skplen;
        // copy the replacement
        strncpy(retptr, replacement, replen);
        retptr += replen;
      }
      // copy the rest of the string.
      strcpy(retptr, oriptr);
    }
    return returned;
  }
}



char *replace4(char *string, char *oldpiece, char *newpiece)
{
  //20-21
   int str_index, newstr_index, oldpiece_index, end,
   new_len, old_len, cpy_len;
   char *c;
   static char newstring[10000];
   if ((c = (char *) strstr(string, oldpiece)) == NULL)
      return string;
   new_len        = strlen(newpiece);
   old_len        = strlen(oldpiece);
   //str            = strlen(string);
   end            = strlen(string) - old_len;
   //int count;
   //for (count = 0; (strstr(oldpiece, newpiece)); ++count){}
   //newstring = malloc(str + (old_len - new_len) * count + 1);

   oldpiece_index = c - string;
   newstr_index = 0;
   str_index = 0;
   while(str_index <= end && c != NULL)
   {
      /* Copy characters from the left of matched pattern occurence */
      cpy_len = oldpiece_index-str_index;
      strncpy(newstring+newstr_index, string+str_index, cpy_len);
      newstr_index += cpy_len;
      str_index    += cpy_len;
      /* Copy replacement characters instead of matched pattern */
      ///*newstring=realloc(newstring,sizeof(newstring)+new_len+old_len+end+newstr_index);
      strcpy(newstring+newstr_index, newpiece);
      newstr_index += new_len;
      str_index    += old_len;
      /* Check for another pattern match */
      if((c = (char *) strstr(string+str_index, oldpiece)) != NULL)
         oldpiece_index = c - string;
   }
   /* Copy remaining characters from the right of last matched pattern */
   strcpy(newstring+newstr_index,
  string+str_index);
  return newstring;
}



char *replace5(char *orig, char *rep, char *with)
{
  //32-33-35
  char *result;
  char *ins;
  char *tmp;
  int len_rep;
  int len_with;
  int len_front;
  int count;
  if (!orig)
      return NULL;
  if (!rep)
      rep = "";
  len_rep = strlen(rep);
  if (!with)
      with = "";
  len_with = strlen(with);
  ins = orig;
  for (count = 0; (tmp = strstr(ins, rep)); ++count) {
      ins = tmp + len_rep;
  }
  tmp = result = malloc(strlen(orig) + (len_with - len_rep) * count + 1);
  if (!result)
      return NULL;
  while (count--) {
      ins = strstr(orig, rep);
      len_front = ins - orig;
      tmp = strncpy(tmp, orig, len_front) + len_front;
      tmp = strcpy(tmp, with) + len_with;
      orig += len_front + len_rep;
  }
  strcpy(tmp, orig);
  return result;
}

replace4()replace6()来自其他,但不是malloc,realloc。

替换()性能测试

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <inttypes.h>
#include <assert.h>
#include <stdint.h>
#include <sys/stat.h>
#include <stdarg.h>
char *temp;
int main()
{
  for(int current=1;current<=80000;current++)
  {
    temp=/*replace6*//*replace3*/replace4("1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890","0","black");
    //printf("%s",temp);
  }
  return 0;
}
  • 如何制作更好的replace()功能?
  • 我可以将malloc,realloc添加到replace4()replace6()吗?

1 个答案:

答案 0 :(得分:2)

非常像您提供的解决方案都不是您可以编写的最快的平衡性能比较。

首先,关于功能4和6有几点:

  • 它们不是可重入的,因为它们使用函数静态缓冲区
  • 显然他们不能超过某个输入大小
  • 如果你不需要可重入的功能,他们提供最快的内存分配,因为他们在第一次通话后没有任何费用
  • 如果你不复制结果,下一次调用将覆盖它,虽然指针仍然有效,可能会导致奇怪的错误
  • 此外,
  • 功能6似乎只能替换一次出现

函数3很可能比其他函数执行得更差,因为它会多次分配内存,可能的例外情况是,如果缓冲区已经足够大,结果可能无效。

对于其余的功能,我注意到它们遵循这种结构模式:

  1. 扫描输入以计算模式发生次数
  2. 为输出分配足够的内存
  3. 在替换要替换的字符时将输入复制到输出
  4. 作为旁注:他们似乎对输入做了不同的检查,我没有检查哪些检查所有常见的角落情况,但是功能8例如根本没有NULL检查,如果你信任你的输入,这可能是正确的,或者它只是扁平的ub。你不想牺牲正确性。
  5. 在这一点上分离和征服并独立解决这些问题可能更容易,因为内存分配可能会占到呼叫成本的很大一部分。因此,目前还不清楚,如果你可以获得任何东西,例如迭代字符串的一部分,在输入的缓存大小的块上进行替换,并在第一个块之后使用启发式估计总大小。然而,使用启发式进行分配可能没问题,但您必须平衡昂贵的重新分配/副本以浪费内存。

    关于1.我会说,如果你真的想知道你需要的确切内存量,并且根据个人经验和SO我会说strstr你无法避免全长扫描通常非常快。我希望在这里优化得到低回报。

    第3部分在这方面是类似的,因为使用c std-lib复制函数可能会导致编译器使用一些手工制作的程序集来破坏你可以编写的所有内容。

    然而,我认为第二步可以很好地优化:

    • 首先请注意,如果替换不再是要替换的序列,则可以进行inplace substring-replace
    • 其次,调用者可能会传入一个大于其包含的字符串的缓冲区,在这种情况下,输入缓冲区可能已经足够大了
    • 第三,如果允许他传入输出缓冲区,调用者可能有其他方法(例如内存池)允许他进行更快的分配

    这意味着如果你提供一个允许调用者传入缓冲区的调用,你可能会更快。

    因此,你可以实现1.作为一个函数,它计算字符串中模式的出现次数(很可能是非常有用的),2。可以由用户完成,3。可以是一个函数假设它收到足够大的输出缓冲区。如果您希望方便地进行一次调用,您可以在第4个帮助器内部使用这些函数,尽管您可能希望至少检查输入的大小并在适当的情况下就地工作。< / p>

    关于您的测试,您目前只进行一次检查,其中涵盖了非常简单的案例。如果您想以有意义的方式测试呼叫的性能,至少应考虑以下因素:

    • 查看影响算法性能的维度,例如:

      • 输入字符串的大小
      • 要替换的序列的大小
      • 要替换的序列的大小
      • 特别检查跨越L1 / L2缓存限制和页面大小倍数时性能如何变化
    • 在预热时间之前进行几次迭代(分支预测等)

    • 操作随机序列,只需确保其维度属性,以防止影响缓存行为,因为您操作相同的100000次
    • 收集足够的样本以消除异常值的影响(我认为你可以通过80k迭代完成这部分)
    • 首先将不同尺寸的测量值分开,独立观察它们,然后将它们融合以获得整体图片

    最终,如果你真的知道你做了什么,要知道你可以看看装配并尝试改进它。此外,如果您可以排除某些情况,因为您知道您在一般问题域的特殊子空间中工作,那么通常可以通过针对您的案例执行特殊解决方案来获得最大收益。