【问题标题】:The fastest way to save graph to file in C在 C 中将图形保存到文件的最快方法
【发布时间】:2018-06-19 14:46:57
【问题描述】:

我有这样一个问题:我需要将 > 百万条边的大图保存到 txt 文件中。每条边都用一个包含 3 个整数的结构表示:从、到、成本。我的任务是编写一个程序,将整个图形快速保存为 txt 文件,格式如下:“from to cost\n”。

我对方法感兴趣,如何做到这一点。 我的想法是创建一个巨大的字符缓冲区,在那里我将每个数字添加到缓冲区而无需反转(首先我得到每个整数的位数,然后将每个数字添加到缓冲区,然后我添加空白/换行符,一直到最后一个数字被添加。

然后我使用 fwrite() 函数将整个缓冲区保存到文件中。

尽管这种方法相对较快,但我看到了执行速度更快的程序。我的问题是:你知道更有效的方法来实施这个程序以获得更快的结果吗?

程序必须是C语言。

typedef struct {
   int edge_start; 
   int edge_count; 
   int parent; 
   int cost; 
} node_t;

typedef struct {
   graph_t *graph;
   node_t *nodes;
   int num_nodes; 
   int start_node; 
} dijkstra_t;

获取位数的函数:

int getNumberOfDigitsBig(int x) {
  if (x >= 10000) {
        if (x >= 10000000) {
            if (x >= 100000000) {
                if (x >= 1000000000)
                    return 9;
                return 8;
            }
            return 7;
        }
        if (x >= 100000) {
            if (x >= 1000000)
                return 6;
            return 5;
        }
        return 4;
    }
    if (x >= 100) {
        if (x >= 1000)
            return 3;
        return 2;
    }
    if (x >= 10)
        return 1;
    return 0;
}

保存功能:

    const dijkstra_t *const dij = (dijkstra_t*)dijkstra;

    if (dij) {
    FILE *f = fopen(filename, "w");
    if (f) {

    int numberOfNodes = dij->num_nodes;
    long bufferLength = numberOfNodes * (9 * 3 + 3);
    buffer = (char *)malloc(bufferLength + 1);
    long bufferCounter = 0;

    int number;
    // printf("i = %d\n", number);
    int counter;
    int digits;

    buffer[bufferCounter++] = '0';
    buffer[bufferCounter++] = ' ';
    buffer[bufferCounter++] = '0';
    buffer[bufferCounter++] = ' ';
    buffer[bufferCounter++] = '-';
    buffer[bufferCounter++] = '1';
    buffer[bufferCounter++] = '\n';

    for(int i = 1; i < numberOfNodes; i++) {
        const node_t *const node = &(dij->nodes[i]);

        number = i;

         digits = getNumberOfDigits(number);
         counter = bufferCounter;

         do {
           buffer[counter + digits] = ZERO + number % 10;
           --digits;
           ++bufferCounter;
         } while(number /= 10);

       buffer[bufferCounter++] = ' ';

       number = node->cost;

       if(number != -1) {

         digits = getNumberOfDigitsBig(number);

         counter = bufferCounter;
         do {
           buffer[counter + digits] = ZERO + number % 10;
           digits = digits - 1;
           bufferCounter = bufferCounter + 1;
         } while(number /= 10);
       } else {
         buffer[bufferCounter++] = '-';
         buffer[bufferCounter++] = '1';

       } 

      buffer[bufferCounter++] = ' ';

        buffer[bufferCounter++] = ' ';

        number = node->parent;

        if(number != -1) {

            digits = getNumberOfDigitsBig(number);

            counter = bufferCounter;
            do {
                buffer[counter + digits] = ZERO + number % 10;
                --digits;
                ++bufferCounter;
            } while(number /= 10);
        } else {
              buffer[bufferCounter++] = '-';
              buffer[bufferCounter++] = '1';

        }

        buffer[bufferCounter++] = '\n';

    }

  fwrite(buffer, 1, bufferCounter, f);

  ret = fclose(f) == 0;
  free(buffer);
  }
 }

感谢关注。

【问题讨论】:

  • 为什么要在内存中准备缓冲区? I/O 函数已经被缓冲,只需一行一行地流出,您甚至不需要分配一个巨大的缓冲区(大小未知)。但是,如果没有您的实际代码,很难说它可以改进的地方。
  • edit您的问题解释这是什么:...txt 文件格式为:“\n”。还要显示结构的定义和你写的代码,否则很难说你的代码。
  • @Nebril 你可以在评论中写[mcve],它会自动扩展为:minimal reproducible example
  • @chux:你问的很有趣,我也有同样的经历。其实是in the Help
  • 我会尝试第一个评论的建议作为替代方案。有限大小的缓冲区甚至可能在写入之前立即对包含完整文件的巨大缓冲区产生积极影响。并且,请不要忘记使用优化的发布编译进行基准测试。 (否则结果没有价值。)我的最后一个想法:内存映射文件通常在出现此类问题时被提及...

标签: c file graph


【解决方案1】:

我假设您需要的是只处理正整数的优化版 printf。我没有对它进行基准测试,但我会尽量少做比较和操作,所以我以那个函数结束:

int printint(FILE *fd, int n) {
    char buffer[32];     // an uint64_t uses max 20 chars in base 10 
    int i = sizeof(buffer);
    do {
        buffer[--i] = '0' + n%10;   // write digits from the right of buffer
        n /= 10;
    } while(n > 0);
    return fwrite(buffer + i, 1, sizeof(buffer) - i, fd);
}

那我就不用大缓冲区了,只依赖 FILE 的默认缓冲 *

然后可以保存代码(或多或少只是从问题的示例开始):

    const dijkstra_t *const dij = (dijkstra_t*)dijkstra;

    if (dij) {
    FILE *f = fopen(filename, "w");
    if (f) {

    int numberOfNodes = dij->num_nodes;

    fputs("0 0 -1\n", f);

    for(int i = 1; i < numberOfNodes; i++) {
        const node_t *const node = &(dij->nodes[i]);

        fputc(' ', f);

        number = node->parent;
        //printf("parent = %d\n", number);
        if(number != -1) {
            printint(number, f);
        } else {
              fwrite("-1", 1, 2, f);

        }

        fputc('\n', f);

    }

  ret = fclose(f) == 0;
  free(buffer);
  }
 }

【讨论】:

  • 感谢您的建议,但您的解决方案比我的解决方案节省时间长 1.5 倍...
  • printf 获取一个字符串作为一个参数,然后进行大量的格式化操作并返回字符串。它比我的方法慢得多(慢 3 到 4 倍)。
【解决方案2】:

你可以通过使用这个“itoa”来提高一点:

void gwf_i2a(char *d, int i, int l) {
  char *e = d + l;
  while (l > 0) {
    e--;
    l--;
    e[0] = '0' + (i % 10);
    i /= 10;
  }
}

原始时间:76 次点击(7.6e-05 秒)。

新时间:39 次点击(3.9e-05 秒)。


来源:

#include <ctime>
#include <iostream>
#include <random>
#include <vector>

#define ZERO '0'

void gwf_i2a(char *d, int i, int l) {
  char *e = d + l;
  while (l > 0) {
    e--;
    l--;
    e[0] = '0' + (i % 10);
    i /= 10;
  }
}

typedef struct {
  int x, y, z;
} graph_t;

typedef struct {
  int edge_start;
  int edge_count;
  int parent;
  int cost;
} node_t;

typedef struct {
  graph_t *graph;
  node_t *nodes;
  int num_nodes;
  int start_node;
} dijkstra_t;

graph_t graph = {111, 222, 3456789};
node_t nodes[] = {{1, 1, 1, 9999}, {2, 2, 2, 8999}, {2, 2, 2, 1234567890}};
dijkstra_t data[] = {&graph, (node_t *)&nodes, 4, 0};

int getNumberOfDigits(int x) {
  if (x >= 100) {
    if (x >= 1000) return 3;
    return 2;
  }
  if (x >= 10) return 1;
  return 0;
}

int getNumberOfDigitsBig(int x) {
  if (x >= 10000) {
    if (x >= 10000000) {
      if (x >= 100000000) {
        if (x >= 1000000000) return 9;
        return 8;
      }
      return 7;
    }
    if (x >= 100000) {
      if (x >= 1000000) return 6;
      return 5;
    }
    return 4;
  }
  if (x >= 100) {
    if (x >= 1000) return 3;
    return 2;
  }
  if (x >= 10) return 1;
  return 0;
}

void save(const char *filename, const dijkstra_t *dijkstra) {
  int ret;
  const dijkstra_t *const dij = (dijkstra_t *)dijkstra;
  char *buffer;
  if (dij) {
    FILE *f = fopen(filename, "w");
    if (f) {
      int numberOfNodes = dij->num_nodes;
      long bufferLength = numberOfNodes * (9 * 3 + 3);
      buffer = (char *)malloc(bufferLength + 1);
      long bufferCounter = 0;

      int number;
      // printf("i = %d\n", number);
      int counter;
      int digits;

      buffer[bufferCounter++] = '0';
      buffer[bufferCounter++] = ' ';
      buffer[bufferCounter++] = '0';
      buffer[bufferCounter++] = ' ';
      buffer[bufferCounter++] = '-';
      buffer[bufferCounter++] = '1';
      buffer[bufferCounter++] = '\n';

      for (int i = 1; i < numberOfNodes; i++) {
        const node_t *const node = &(dij->nodes[i]);

        number = i;

        digits = getNumberOfDigits(number);
        counter = bufferCounter;

        do {
          buffer[counter + digits] = ZERO + number % 10;
          --digits;
          ++bufferCounter;
        } while (number /= 10);

        buffer[bufferCounter++] = ' ';

        number = node->cost;

        if (number != -1) {
          digits = getNumberOfDigitsBig(number);

          counter = bufferCounter;
          do {
            buffer[counter + digits] = ZERO + number % 10;
            digits = digits - 1;
            bufferCounter = bufferCounter + 1;
          } while (number /= 10);
        } else {
          buffer[bufferCounter++] = '-';
          buffer[bufferCounter++] = '1';
        }

        buffer[bufferCounter++] = ' ';

        buffer[bufferCounter++] = ' ';

        number = node->parent;

        if (number != -1) {
          digits = getNumberOfDigitsBig(number);

          counter = bufferCounter;
          do {
            buffer[counter + digits] = ZERO + number % 10;
            --digits;
            ++bufferCounter;
          } while (number /= 10);
        } else {
          buffer[bufferCounter++] = '-';
          buffer[bufferCounter++] = '1';
        }

        buffer[bufferCounter++] = '\n';
      }

      fwrite(buffer, 1, bufferCounter, f);

      ret = fclose(f) == 0;
      free(buffer);
    }
  }
}

void new_save(const char *filename, const dijkstra_t *dijkstra) {
  int ret;
  const dijkstra_t *const dij = (dijkstra_t *)dijkstra;
  char *buffer;
  if (dij) {
    FILE *f = fopen(filename, "w");
    if (f) {
      int numberOfNodes = dij->num_nodes;
      long bufferLength = numberOfNodes * (9 * 3 + 3);
      buffer = (char *)malloc(bufferLength + 1);
      long bufferCounter = 0;

      int number;
      int counter;
      int digits;

      buffer[bufferCounter++] = '0';
      buffer[bufferCounter++] = ' ';
      buffer[bufferCounter++] = '0';
      buffer[bufferCounter++] = ' ';
      buffer[bufferCounter++] = '-';
      buffer[bufferCounter++] = '1';
      buffer[bufferCounter++] = '\n';

      for (int i = 1; i < numberOfNodes; i++) {
        const node_t *const node = &(dij->nodes[i]);
        int len = getNumberOfDigits(i) + 1;

        gwf_i2a((char *)&buffer[bufferCounter], i, len);
        bufferCounter += len;
        buffer[bufferCounter++] = ' ';

        number = node->cost;

        if (number != -1) {
          len = getNumberOfDigitsBig(number) + 1;
          gwf_i2a((char *)&buffer[bufferCounter], number, len);
          bufferCounter += len;

        } else {
          buffer[bufferCounter++] = '-';
          buffer[bufferCounter++] = '1';
        }

        buffer[bufferCounter++] = ' ';

        buffer[bufferCounter++] = ' ';

        number = node->parent;

        if (number != -1) {
          digits = getNumberOfDigitsBig(number);

          counter = bufferCounter;
          do {
            buffer[counter + digits] = ZERO + number % 10;
            --digits;
            ++bufferCounter;
          } while (number /= 10);
        } else {
          buffer[bufferCounter++] = '-';
          buffer[bufferCounter++] = '1';
        }

        buffer[bufferCounter++] = '\n';
      }

      fwrite(buffer, 1, bufferCounter, f);

      ret = fclose(f) == 0;
      free(buffer);
    }
  }
}
void original() {
  clock_t t;
  t = clock();
  save("bogus.txt", data);
  t = clock() - t;
  std::cout << "original: " << t << " clicks (" << ((float)t) / CLOCKS_PER_SEC
            << " seconds)." << std::endl;
}
void new_test() {
  clock_t t;
  t = clock();
  new_save("new_bogus.txt", data);
  t = clock() - t;
  std::cout << "NEW: " << t << " clicks (" << ((float)t) / CLOCKS_PER_SEC
            << " seconds)." << std::endl;
}
int main(int argc, char **argv) {
  original();
  new_test();

  return 0;
}

【讨论】:

  • 您的建议已经有所改进,我想说的是,它在小图( 1000000 条边)上快 20-30% .我想知道是否可以进行任何其他类型的改进来改进代码。顺便说一句,也许你可以建议我用边缘阅读这个文件?我有它足够快,但它可能会更快。我可以显示我的代码
  • 如果节点和图形在内存中太远,我唯一能想到的是预处理您的数组以避免内存缓存未命中,但这可能很困难,因为在所有情况。
  • 另外,我认为使用随机数据生成器构建一个更完整的示例(一个工作示例)以供我们测试是一个好主意。
  • 这是生成器的代码 - paste.ofcode.org/sBFeT3EU657WT8EsPWSU26 我考虑将所有不接受负值的整数重新输入到 uint32_t。我也得帮忙
【解决方案3】:

[于 2018-01-13 重写。] 标准 I/O(printf() 等)在将数字数据转换为文本形式方面确实相对较慢。这里的问题是输出表格的行

  

其中三个都是十进制表示法的无符号(32 位)整数,或者 -1。为简单起见,我们将值 UINT32_MAX (4294967295) 保留为 -1

我建议采用两种方法:

  1. 从右到左构造每条记录。这避免了检查数字中有多少位的需要。

  2. 一次缓冲多条记录。这减少了fwrite() 调用的数量,代价是适度的动态分配缓冲区。

    请注意,这意味着每个块中的记录必须从后到先处理,以保持正确的顺序。

考虑以下代码。请注意,我已将node_tdijkstra_t 的定义缩减为实际使用的字段,以便可以按原样编译以下示例。另请注意,必须使用UINT32_MAX,而不是-1 for parentcost,因为它们的类型现在是uint32_t

#include <stdlib.h>
#include <stdint.h>
#include <limits.h>
#include <stdio.h>

typedef struct {
    uint32_t    parent;     /* Use UINT32_MAX for -1 */
    uint32_t    cost;       /* Use UINT32_MAX for -1 */
} node_t;

typedef struct {
    node_t     *nodes;
    uint32_t    num_nodes;
} dijkstra_t;

/* This function will store an unsigned 32-bit value
   in decimal form, ending at 'end'.
   UINT32_MAX will be written as "-1", however.
   Returns a pointer to the start of the value.
*/
static inline char *prepend_value(char *end, uint32_t value)
{
    if (value == UINT32_MAX) {
        *(--end) = '1';
        *(--end) = '-';
    } else {
        do {
            *(--end) = '0' + (value % 10u);
            value /= 10u;
        } while (value);
    }
    return end;
}

/* Each record consists of three unsigned 32-bit integers,
   each at most 10 characters, with spaces in between
   and a newline at end. Thus, at most 33 characters. */
#define  RECORD_MAXLEN  33
/* We process records in chunks of 16384.
   Maximum number of records (nodes) is 2**32 - 2 - RECORD_CHUNK,
   or 4,294,950,910 in this case. */
#define  RECORD_CHUNK   16384
/* Each chunk of record is up to CHUNK_CHARS long.
   (Roughly half a megabyte in this case.) */
#define  CHUNK_CHARS   (RECORD_MAXLEN * RECORD_CHUNK)

/* Save the edges in a graph to a stream.
   Returns 0 if success, -1 if an error occurs.
*/
int save_edges(dijkstra_t *dij, FILE *out)
{
    if (dij && out && !ferror(out)) {
        const int       nodes = dij->num_nodes;
        const node_t   *node  = dij->nodes;

        const uint32_t  root_parent = dij->nodes->parent;
        const uint32_t  root_cost   = dij->nodes->cost;

        char           *buf, *end, *ptr;
        uint32_t        o;

        /* Allocate memory for the chunk buffer. */
        buf = malloc(CHUNK_CHARS);
        if (!buf)
            return -1;

        end = buf + CHUNK_CHARS;

        /* Temporarily, we reset the root node parent
           to UINT32_MAX and cost to 0, so that the
           very first record in the output is "0 0 -1". */
        dij->nodes->cost   = 0;
        dij->nodes->parent = UINT32_MAX;

        for (o = 0; o < nodes; o += RECORD_CHUNK) {
            uint32_t  i = (o + RECORD_CHUNK < nodes) ? o + RECORD_CHUNK : nodes;

            /* Fill buffer back-to-front. */
            ptr = end;
            while (i-->o) {
                const node_t  *curr = node + i;

                /* Format: <i> ' ' <cost> ' ' <parent> '\n' */
                /* We construct the record from right to left. */

                *(--ptr) = '\n';
                ptr = prepend_value(ptr, curr->parent);
                *(--ptr) = ' ';
                ptr = prepend_value(ptr, curr->cost);
                *(--ptr) = ' ';
                ptr = prepend_value(ptr, i);
            }

            /* Write the chunk buffer out. */
            if (fwrite(ptr, 1, (size_t)(end - ptr), out) != (size_t)(end - ptr)) {
                dij->nodes->cost = root_cost;
                dij->nodes->parent = root_parent;
                free(buf);
                return -1;
            }
        }

        /* Reset root node, and free the buffer. */
        dij->nodes->cost = root_cost;
        dij->nodes->parent = root_parent;
        free(buf);

        /* Check for write errors. */
        if (fflush(out))
            return -1;
        if (ferror(out))
            return -1;

        /* Success. */
        return 0;
    }

    return -1;
}

如果我们可以使用 POSIX 低级 I/O(&lt;unistd.h&gt; 中的open()close()write()fstat()),则可以实现额外的加速。当目的地是管道或设备时,我们可以直接写入数据;当目标是文件时,我们应该写入st_blksize 的倍数块,以避免读取-修改-写入循环。与标准 I/O 不同,对于低级 I/O,我们只需一个“溢出”缓冲区st_blksize 就可以做到这一点,而无需在内存中复制整个块缓冲区。但是,由于该问题未标记为,因此我将避免沿这些边缘进行进一步讨论。


OP 表示他们自己的版本仍然更快。我发现这很难相信,因为它比我上面的版本做得更多。当我检查时,在我的机器上,一个大型数据集(比如 100,000,000)不能在单个 fwrite() 调用中写入,因为它只进行部分写入;实际写入整个数据集需要一个循环。因此,在我看来,OP 用来比较不同版本的基准是非常可疑的。

请考虑以下微基准测试。它生成一个单链表,并使用外部编译的save_graph() 函数将其输出(到标准输出)。实现了三个版本:null,根本不保存任何东西; antonkretov,用于 OP 的实现(适用于此处工作);和 nominalanimal,我的。

生成文件

CC      := gcc
CFLAGS  := -std=c99 -O2 -Wall
LDFLAGS :=
BINS    := test-null test-antonkretov test-nominalanimal
NODES   := 100000000

.PHONY: all clean run

all: clean $(BINS)

clean:
        rm -f $(BINS) *.o

%.o: %.c
        $(CC) $(CFLAGS) -c $^


test-null: main.o data-null.o
        $(CC) $(CFLAGS) $^ -o $@

test-antonkretov: main.o data-antonkretov.o
        $(CC) $(CFLAGS) $^ -o $@

test-nominalanimal: main.o data-nominalanimal.o
        $(CC) $(CFLAGS) $^ -o $@

run: $(BINS)
        @echo "Testing $(NODES) nodes."
        @./test-null $(NODES) > /dev/null

        @echo "Overhead (nothing saved):"
        @bash -c 'time ./test-null $(NODES) > /dev/null'
        @echo ""

        @echo "Anton Kretov:"
        @bash -c 'time ./test-antonkretov $(NODES) > /dev/null'
        @echo ""

        @echo "Nominal Animal:"
        @bash -c 'time ./test-nominalanimal $(NODES) > /dev/null'
        @echo ""

请注意,本论坛将Tabs转换为空格,而Makefile格式需要缩进才能使用空格,因此如果将上述内容复制并粘贴到文件中,则需要运行例如sed -e 's|^ *|\t|' -i Makefile 修复它。

data.h

#ifndef   DATA_H
#define   DATA_H
#include <stdint.h>
#include <limits.h>
#include <stdio.h>

#define  INVALID_COST    UINT32_MAX
#define  INVALID_PARENT  UINT32_MAX

typedef struct {
    uint32_t    parent;     /* Use INVALID_PARENT for -1 */
    uint32_t    cost;       /* Use INVALID_COST for -1 */
} node_t;

typedef struct {
    node_t     *nodes;
    uint32_t    num_nodes;
} dijkstra_t;

int save_graph(dijkstra_t *, FILE *);

#endif /* DATA_H */

data-null.c,用于测量运行时开销:

#include "data.h"

int save_graph(dijkstra_t *dij, FILE *out)
{
    /* Does not do anything */
    return 0;
}

data-antonkretov.c,OP 的保存例程版本,用于比较:

#include <stdlib.h>
#include "data.h"

int getNumberOfDigits(uint32_t x)
{
    if (x >= 10000) {
        if (x >= 10000000) {
            if (x >= 100000000) {
                if (x >= 1000000000)
                    return 9;
                return 8;
            }
            return 7;
        }
        if (x >= 100000) {
            if (x >= 1000000)
                return 6;
            return 5;
        }
        return 4;
    }
    if (x >= 100) {
        if (x >= 1000)
            return 3;
        return 2;
    }
    if (x >= 10)
        return 1;
    return 0;
}

int save_graph(dijkstra_t *dij, FILE *out)
{
    uint32_t    numberOfNodes = dij->num_nodes;
    size_t      bufferLength = numberOfNodes * (size_t)33;
    size_t      bufferCounter = 0, counter;
    size_t      bytes;
    uint32_t    number, digits, i;
    char       *buffer;

    if ((size_t)(bufferLength / 33) != numberOfNodes)
        return -1;

    buffer = malloc(bufferLength);
    if (!buffer)
        return -1;

    buffer[bufferCounter++] = '0';
    buffer[bufferCounter++] = ' ';
    buffer[bufferCounter++] = '0';
    buffer[bufferCounter++] = ' ';
    buffer[bufferCounter++] = '-';
    buffer[bufferCounter++] = '1';
    buffer[bufferCounter++] = '\n';

    for (i = 1; i < numberOfNodes; i++) {
        const node_t *const node = dij->nodes + i;

        number = i;
        digits = getNumberOfDigits(number);
        counter = bufferCounter;
        do {
            buffer[counter + digits] = '0' + (number % 10u);
            --digits;
            ++bufferCounter;
        } while (number /= 10u);

        buffer[bufferCounter++] = ' ';

        number = node->cost;
        if (number != UINT32_MAX) {
            digits = getNumberOfDigits(number);
            counter = bufferCounter;
            do {
                buffer[counter + digits] = '0' + (number % 10u);
                --digits;
                ++bufferCounter;
            } while (number /= 10u);
        } else {
            buffer[bufferCounter++] = '-';
            buffer[bufferCounter++] = '1';
        }

        buffer[bufferCounter++] = ' ';

        number = node->parent;
        if (number != UINT32_MAX) {
            digits = getNumberOfDigits(number);
            counter = bufferCounter;
            do {
                buffer[counter + digits] = '0' + (number % 10u);
                --digits;
                ++bufferCounter;
            } while (number /= 10u);
        } else {
            buffer[bufferCounter++] = '-';
            buffer[bufferCounter++] = '1';
        }

        buffer[bufferCounter++] = '\n';
    }

    counter = 0;
    while (counter < bufferCounter) {
        bytes = fwrite(buffer + counter, 1, bufferCounter - counter, out);
        if (!bytes) {
            free(buffer);
            return -1;
        }
        counter += bytes;
    }

    free(buffer);

    return 0;
}

data-nominalanimal.c,我的分块从后到前版本的保存例程:

#include <stdlib.h>
#include "data.h"

/* This function will store an unsigned 32-bit value
   in decimal form, ending at 'end'.
   UINT32_MAX will be written as "-1", however.
   Returns a pointer to the start of the value.
*/
static inline char *prepend_value(char *end, uint32_t value)
{
    if (value == UINT32_MAX) {
        *(--end) = '1';
        *(--end) = '-';
    } else {
        do {
            *(--end) = '0' + (value % 10u);
            value /= 10u;
        } while (value);
    }
    return end;
}

/* Each record consists of three unsigned 32-bit integers,
   each at most 10 characters, with spaces in between
   and a newline at end. Thus, at most 33 characters. */
#define  RECORD_MAXLEN  33
/* We process records in chunks of 16384.
   Maximum number of records (nodes) is 2**32 - 2 - RECORD_CHUNK,
   or 4,294,950,910 in this case. */
#define  RECORD_CHUNK   16384
/* Each chunk of record is up to CHUNK_CHARS long.
   (Roughly half a megabyte in this case.) */
#define  CHUNK_CHARS   (RECORD_MAXLEN * RECORD_CHUNK)

/* Save the edges in a graph to a stream.
   Returns 0 if success, -1 if an error occurs.
*/
int save_graph(dijkstra_t *dij, FILE *out)
{
    if (dij && out && !ferror(out)) {
        const int       nodes = dij->num_nodes;
        const node_t   *node  = dij->nodes;

        const uint32_t  root_parent = dij->nodes->parent;
        const uint32_t  root_cost   = dij->nodes->cost;

        char           *buf, *end, *ptr;
        uint32_t        o;

        /* Allocate memory for the chunk buffer. */
        buf = malloc(CHUNK_CHARS);
        if (!buf)
            return -1;

        end = buf + CHUNK_CHARS;

        /* Temporarily, we reset the root node parent
           to UINT32_MAX and cost to 0, so that the
           very first record in the output is "0 0 -1". */
        dij->nodes->cost   = 0;
        dij->nodes->parent = UINT32_MAX;

        for (o = 0; o < nodes; o += RECORD_CHUNK) {
            uint32_t  i = (o + RECORD_CHUNK < nodes) ? o + RECORD_CHUNK : nodes;

            /* Fill buffer back-to-front. */
            ptr = end;
            while (i-->o) {
                const node_t  *curr = node + i;

                /* Format: <i> ' ' <cost> ' ' <parent> '\n' */
                /* We construct the record from right to left. */
                *(--ptr) = '\n';
                ptr = prepend_value(ptr, curr->parent);
                *(--ptr) = ' ';
                ptr = prepend_value(ptr, curr->cost);
                *(--ptr) = ' ';
                ptr = prepend_value(ptr, i);
            }

            /* Write buffer. */
            if (fwrite(ptr, 1, (size_t)(end - ptr), out) != (size_t)(end - ptr)) {
                dij->nodes->cost = root_cost;
                dij->nodes->parent = root_parent;
                free(buf);
                return -1;
            }
        }

        /* Reset root node, and free the buffer. */
        dij->nodes->cost = root_cost;
        dij->nodes->parent = root_parent;
        free(buf);

        if (fflush(out))
            return -1;
        if (ferror(out))
            return -1;

        return 0;
    }

    return -1;
}

最后是主程序本身,ma​​in.c,它生成数据并调用save_graph() 函数:

#include <stdlib.h>
#include <inttypes.h>
#include <limits.h>
#include <string.h>
#include "data.h"

#define  EDGES_MAX  4294901759

int main(int argc, char *argv[])
{
    dijkstra_t graph;
    size_t     bytes;
    uint32_t   edges, i;
    char       dummy;

    if (argc != 2 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
        fprintf(stderr, "\nUsage: %s EDGES\n\n", argv[0]);
        return EXIT_SUCCESS;
    }
    if (sscanf(argv[1], " %" SCNu32 " %c", &edges, &dummy) != 1 || edges < 1 || edges > EDGES_MAX) {
        fprintf(stderr, "%s: Invalid number of edges.\n", argv[1]);
        return EXIT_FAILURE;
    }

    bytes = (1 + (size_t)edges) * sizeof graph.nodes[0];
    if ((size_t)(bytes / (1 + (size_t)edges)) != sizeof graph.nodes[0]) {
        fprintf(stderr, "%s: Too many edges.\n", argv[1]);
        return EXIT_FAILURE;
    }
    graph.num_nodes = edges + 1;
    graph.nodes = malloc(bytes);
    if (!graph.nodes) {
        fprintf(stderr, "%s: Too many edges: out of memory.\n", argv[1]);
        return EXIT_FAILURE;
    }

    /* Generate a graph; no randomness, to keep timing steady. */
    graph.nodes[0].parent = INVALID_COST;
    graph.nodes[0].cost = 0;
    for (i = 1; i <= edges; i++) {
        graph.nodes[i].parent = i - 1;
        graph.nodes[i].cost = 1 + (i % 10);
    }

    /* Print graph. */
    if (save_graph(&graph, stdout)) {
        fprintf(stderr, "Write error!\n");
        return EXIT_FAILURE;
    }

    /* Done. */
    return EXIT_SUCCESS;
}

运行make clean run(或make NODES=100000000 clean run)会重新编译基准测试并测量它们的运行时间,以获取具有 100,000,000 个节点的图。在我的机器上,输出是

Testing 100000000 nodes.
Overhead (nothing saved):

real0m0.514s
user0m0.297s
sys0m0.217s

Anton Kretov:

real0m4.059s
user0m3.379s
sys0m0.680s

Nominal Animal:

real0m3.336s
user0m3.151s
sys0m0.184s

这表明我的速度明显更快。如果我们忽略开销(生成图表),我的需要大约 2.8 秒的实时时间来将数据保存到/dev/null,而 OP 大约需要 3.5 秒。换句话说,我的速度提高了 20%。

请务必注意,这两个测试确实会产生完全相同的输出。例如,./test-nominalanimal 100000000 | sha256sum -./test-antonkretov 100000000 | sha256sum - 都显示完全相同的 SHA256 校验和,7504a1c97167701297c03c4aab8b0f20c5cac82a50128074d6e09c474353d0f8。 (您也可以将输出保存到文件中,然后比较它们;两者的长度正好是 1,987,777,795 字节,并且包含完全相同的数据。我确实检查了。)

如果您想运行将数据存储到存储器的基准测试,为了公平比较,您需要从冷缓存开始。否则,您运行基准测试的顺序将严重影响它们的时间安排。

【讨论】:

  • 其实我没有生成任何图表。我唯一得到的是带有所有边缘的txt文件。我的任务是找到从第一个节点到其他节点的所有最短路径。然后我必须将最短路径保存到 txt 文件。因此,bunkry 文件不是选项。
  • 我刚刚评论了你的评论。我无法理解你的回复,因为你肯定在写别的东西。你为什么评论 rand()?我根本不使用它。正如我所说:我唯一需要实现的就是快速保存。我只是得到了具有数百万条边的结构,我必须将它们保存到 .txt 文件中。然后你写了我不允许使用的二进制文件(我也不明白,为什么)。我已经运行了你的保存代码(output_edge() 函数),它产生的性能要慢得多。我想你评论了图表创建者的代码,但我根本不生成图表
  • @AntonKretov:现在我明白了。我看到了您的原始帖子,然后您的评论在 paste.ofcode.org 上提到了您的生成器,而不是在两者之间对您的问题进行的编辑。 (我不知道为什么,可能是连接问题。)是的,有很多方法可以加快以 ASCII 文本形式保存和加载此类图形的速度。我将使用您问题中现在显示的结构重写我的答案以仅进行保存。
  • 执行您的函数时出现分段错误。在最后一个 prepend_value() 之后它会抛出这样的错误
  • 我设法编辑了您的代码以使其工作(错误是在分配指向 buf 中第一个元素的指针以结束时,您必须指向最后一个。但我的方法是还是比你的快。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-10-01
  • 2012-05-15
  • 2013-12-10
相关资源
最近更新 更多