From 0e739f34801ff6810064a8fac570f6be2b61ae70 Mon Sep 17 00:00:00 2001 From: David Robillard Date: Fri, 13 Aug 2021 20:31:57 -0400 Subject: Simplify output stream API This makes the paging mechanism an internal detail once again. While it's conceptually elegant to simply have a single write interface and have the block dumper just be another implementation of that, unfortunately it is not practical. The inlining of serd_block_dumper_write() is a significant performance boost, because it avoids a non-inlinable function call of overhead per character. Compared to the SerdByteSink approach, this removes the burden and overhead of needing to dynamically allocate the structure itself. --- src/block_dumper.c | 59 +++++++++++++++++++++++++ src/block_dumper.h | 88 +++++++++++++++++++++++++++++++++++++ src/byte_sink.c | 124 ---------------------------------------------------- src/byte_sink.h | 66 ---------------------------- src/node_syntax.c | 14 +++--- src/output_stream.c | 89 +++++++++++++++++++++++++++++++++++++ src/writer.c | 31 ++++++++----- 7 files changed, 264 insertions(+), 207 deletions(-) create mode 100644 src/block_dumper.c create mode 100644 src/block_dumper.h delete mode 100644 src/byte_sink.c delete mode 100644 src/byte_sink.h create mode 100644 src/output_stream.c (limited to 'src') diff --git a/src/block_dumper.c b/src/block_dumper.c new file mode 100644 index 00000000..6ffb0013 --- /dev/null +++ b/src/block_dumper.c @@ -0,0 +1,59 @@ +/* + Copyright 2011-2021 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#include "block_dumper.h" +#include "system.h" + +#include "serd/serd.h" + +#include + +SerdStatus +serd_block_dumper_open(SerdBlockDumper* const dumper, + SerdOutputStream* const output, + const size_t block_size) +{ + if (!block_size) { + return SERD_ERR_BAD_ARG; + } + + dumper->out = output; + dumper->buf = NULL; + dumper->size = 0u; + dumper->block_size = block_size; + + if (block_size == 1) { + return SERD_SUCCESS; + } + + dumper->buf = (char*)serd_allocate_buffer(block_size); + return dumper->buf ? SERD_SUCCESS : SERD_ERR_INTERNAL; +} + +void +serd_block_dumper_flush(SerdBlockDumper* const dumper) +{ + if (dumper->out->stream && dumper->block_size > 1 && dumper->size > 0) { + dumper->out->write(dumper->buf, 1, dumper->size, dumper->out->stream); + dumper->size = 0; + } +} + +void +serd_block_dumper_close(SerdBlockDumper* const dumper) +{ + serd_free_aligned(dumper->buf); +} diff --git a/src/block_dumper.h b/src/block_dumper.h new file mode 100644 index 00000000..7c718566 --- /dev/null +++ b/src/block_dumper.h @@ -0,0 +1,88 @@ +/* + Copyright 2011-2021 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#ifndef SERD_BLOCK_DUMPER_H +#define SERD_BLOCK_DUMPER_H + +#include "serd/serd.h" + +#include +#include + +typedef struct { + SerdOutputStream* SERD_ALLOCATED out; ///< Output stream to write to + char* SERD_ALLOCATED buf; ///< Local buffer if needed + size_t size; ///< Bytes pending for this block + size_t block_size; ///< Block size to write in bytes +} SerdBlockDumper; + +/** + Set up a new output stream wrapper that writes in blocks. + + This allocates a buffer internally, which must be eventually freed by + calling serd_block_dumper_close(). +*/ +SerdStatus +serd_block_dumper_open(SerdBlockDumper* SERD_NONNULL dumper, + SerdOutputStream* SERD_NONNULL output, + size_t block_size); + +void +serd_block_dumper_flush(SerdBlockDumper* SERD_NONNULL dumper); + +void +serd_block_dumper_close(SerdBlockDumper* SERD_NONNULL dumper); + +/** + Write some bytes to the page writer. + + This works like any other SerdWriteFunc, but will append to an internal + buffer and only actually write to the output when a whole block is ready. +*/ +static inline size_t +serd_block_dumper_write(const void* SERD_NONNULL buf, + const size_t size, + const size_t nmemb, + SerdBlockDumper* SERD_NONNULL const dumper) +{ + if (dumper->block_size == 1) { + return dumper->out->write(buf, size, nmemb, dumper->out->stream); + } + + size_t len = size * nmemb; + const size_t orig_len = len; + while (len) { + const size_t space = dumper->block_size - dumper->size; + const size_t n = space < len ? space : len; + + // Write as much as possible into the remaining buffer space + memcpy(dumper->buf + dumper->size, buf, n); + dumper->size += n; + buf = (const char*)buf + n; + len -= n; + + // Flush page if buffer is full + if (dumper->size == dumper->block_size) { + dumper->out->write( + dumper->buf, 1, dumper->block_size, dumper->out->stream); + dumper->size = 0; + } + } + + return orig_len; +} + +#endif // SERD_DUMPER_H diff --git a/src/byte_sink.c b/src/byte_sink.c deleted file mode 100644 index b5b4039e..00000000 --- a/src/byte_sink.c +++ /dev/null @@ -1,124 +0,0 @@ -/* - Copyright 2011-2020 David Robillard - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -*/ - -#include "byte_sink.h" - -#include "serd_config.h" -#include "system.h" - -#include "serd/serd.h" - -#include -#include -#include -#include - -#if USE_POSIX_FADVISE && USE_FILENO -# include -#endif - -SerdByteSink* -serd_byte_sink_new_buffer(SerdBuffer* const buffer) -{ - assert(buffer); - - return serd_byte_sink_new_function( - serd_buffer_write, serd_buffer_close, buffer, 1u); -} - -SerdByteSink* -serd_byte_sink_new_function(const SerdWriteFunc write_func, - const SerdStreamCloseFunc close_func, - void* const stream, - const size_t block_size) -{ - if (!block_size) { - return NULL; - } - - SerdByteSink* sink = (SerdByteSink*)calloc(1, sizeof(SerdByteSink)); - - sink->write_func = write_func; - sink->close_func = close_func; - sink->stream = stream; - sink->block_size = block_size; - - if (block_size > 1) { - sink->buf = (char*)serd_allocate_buffer(block_size); - } - - return sink; -} - -SerdByteSink* -serd_byte_sink_new_filename(const char* const path, const size_t block_size) -{ - assert(path); - - if (!block_size) { - return NULL; - } - - FILE* const file = fopen(path, "wb"); - if (!file) { - return NULL; - } - -#if USE_POSIX_FADVISE && USE_FILENO - posix_fadvise(fileno(file), 0, 0, POSIX_FADV_SEQUENTIAL); -#endif - - return serd_byte_sink_new_function( - (SerdWriteFunc)fwrite, (SerdStreamCloseFunc)fclose, file, block_size); -} - -void -serd_byte_sink_flush(SerdByteSink* sink) -{ - assert(sink); - - if (sink->stream && sink->block_size > 1 && sink->size > 0) { - sink->write_func(sink->buf, 1, sink->size, sink->stream); - sink->size = 0; - } -} - -SerdStatus -serd_byte_sink_close(SerdByteSink* sink) -{ - assert(sink); - - serd_byte_sink_flush(sink); - - if (sink->stream && sink->close_func) { - const int st = sink->close_func(sink->stream); - sink->stream = NULL; - return st ? SERD_ERR_UNKNOWN : SERD_SUCCESS; - } - - sink->stream = NULL; - return SERD_SUCCESS; -} - -void -serd_byte_sink_free(SerdByteSink* const sink) -{ - if (sink) { - serd_byte_sink_close(sink); - serd_free_aligned(sink->buf); - free(sink); - } -} diff --git a/src/byte_sink.h b/src/byte_sink.h deleted file mode 100644 index d117a589..00000000 --- a/src/byte_sink.h +++ /dev/null @@ -1,66 +0,0 @@ -/* - Copyright 2011-2020 David Robillard - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -*/ - -#ifndef SERD_BYTE_SINK_H -#define SERD_BYTE_SINK_H - -#include "serd/serd.h" - -#include -#include - -struct SerdByteSinkImpl { - SerdWriteFunc write_func; ///< User sink for TO_FUNCTION - SerdStreamCloseFunc close_func; ///< Optional function to close stream - void* stream; ///< User data for write_func - char* buf; ///< Local buffer iff block_size > 1 - size_t size; ///< Bytes written so far in this chunk - size_t block_size; ///< Size of chunks to write -}; - -static inline size_t -serd_byte_sink_write(const void* buf, size_t len, SerdByteSink* const sink) -{ - if (len == 0) { - return 0; - } - - if (sink->block_size == 1) { - return sink->write_func(buf, 1, len, sink->stream); - } - - const size_t orig_len = len; - while (len) { - const size_t space = sink->block_size - sink->size; - const size_t n = space < len ? space : len; - - // Write as much as possible into the remaining buffer space - memcpy(sink->buf + sink->size, buf, n); - sink->size += n; - buf = (const char*)buf + n; - len -= n; - - // Flush page if buffer is full - if (sink->size == sink->block_size) { - sink->write_func(sink->buf, 1, sink->block_size, sink->stream); - sink->size = 0; - } - } - - return orig_len; -} - -#endif // SERD_BYTE_SINK_H diff --git a/src/node_syntax.c b/src/node_syntax.c index 1dadc7b8..d2768c3e 100644 --- a/src/node_syntax.c +++ b/src/node_syntax.c @@ -98,22 +98,22 @@ serd_node_to_syntax_in(const SerdNode* const node, const SerdSyntax syntax, const SerdEnv* const env) { - SerdWorld* const world = serd_world_new(); - SerdBuffer buffer = {NULL, 0}; - SerdByteSink* const out = serd_byte_sink_new_buffer(&buffer); - SerdWriter* const writer = serd_writer_new(world, syntax, 0, env, out); + SerdWorld* const world = serd_world_new(); + SerdBuffer buffer = {NULL, 0}; + SerdOutputStream out = serd_open_output_buffer(&buffer); + SerdWriter* const writer = serd_writer_new(world, syntax, 0, env, &out, 1); char* result = NULL; if (!serd_writer_write_node(writer, node) && !serd_writer_finish(writer) && - !serd_byte_sink_close(out)) { + !serd_close_output(&out)) { result = (char*)buffer.buf; } else { - serd_byte_sink_close(out); + serd_close_output(&out); free(buffer.buf); } serd_writer_free(writer); - serd_byte_sink_free(out); + serd_close_output(&out); serd_world_free(world); return result; diff --git a/src/output_stream.c b/src/output_stream.c new file mode 100644 index 00000000..1254aa4f --- /dev/null +++ b/src/output_stream.c @@ -0,0 +1,89 @@ +/* + Copyright 2011-2021 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#include "serd_config.h" + +#include "serd/serd.h" + +// IWYU pragma: no_include + +#include +#include +#include + +#if USE_POSIX_FADVISE && USE_FILENO +# include +#endif + +SerdOutputStream +serd_open_output_stream(SerdWriteFunc const write_func, + SerdStreamCloseFunc const close_func, + void* const stream) +{ + assert(write_func); + + SerdOutputStream output = {stream, write_func, close_func}; + return output; +} + +SerdOutputStream +serd_open_output_buffer(SerdBuffer* const buffer) +{ + assert(buffer); + + return serd_open_output_stream(serd_buffer_write, serd_buffer_close, buffer); +} + +SerdOutputStream +serd_open_output_file(const char* const path) +{ + assert(path); + +#ifdef __GLIBC__ + FILE* const file = fopen(path, "wbe"); +#else + FILE* const file = fopen(path, "wb"); +#endif + + if (!file) { + const SerdOutputStream failure = {NULL, NULL, NULL}; + return failure; + } + +#if USE_POSIX_FADVISE && USE_FILENO + posix_fadvise(fileno(file), 0, 0, POSIX_FADV_SEQUENTIAL); +#endif + + return serd_open_output_stream( + (SerdWriteFunc)fwrite, (SerdStreamCloseFunc)fclose, file); +} + +SerdStatus +serd_close_output(SerdOutputStream* const output) +{ + int ret = 0; + + if (output) { + if (output->close && output->stream) { + ret = output->close(output->stream); + output->stream = NULL; + } + + output->stream = NULL; + } + + return ret ? SERD_ERR_UNKNOWN : SERD_SUCCESS; +} diff --git a/src/writer.c b/src/writer.c index 305c72e1..a9141f78 100644 --- a/src/writer.c +++ b/src/writer.c @@ -16,7 +16,7 @@ #include "writer.h" -#include "byte_sink.h" +#include "block_dumper.h" #include "env.h" #include "node.h" #include "sink.h" @@ -128,7 +128,7 @@ struct SerdWriterImpl { SerdURIView root_uri; WriteContext* anon_stack; size_t anon_stack_size; - SerdByteSink* byte_sink; + SerdBlockDumper output; WriteContext context; Sep last_sep; int indent; @@ -251,7 +251,8 @@ ctx(SerdWriter* writer, const SerdField field) SERD_WARN_UNUSED_RESULT static size_t sink(const void* buf, size_t len, SerdWriter* writer) { - const size_t written = serd_byte_sink_write(buf, len, writer->byte_sink); + const size_t written = serd_block_dumper_write(buf, 1, len, &writer->output); + if (written != len) { if (errno) { char message[1024] = {0}; @@ -1306,6 +1307,9 @@ serd_writer_finish(SerdWriter* writer) } free_context(writer); + + serd_block_dumper_flush(&writer->output); + writer->indent = 0; writer->context = WRITE_CONTEXT_NULL; writer->empty = true; @@ -1313,15 +1317,21 @@ serd_writer_finish(SerdWriter* writer) } SerdWriter* -serd_writer_new(SerdWorld* world, - SerdSyntax syntax, - SerdWriterFlags flags, - const SerdEnv* env, - SerdByteSink* byte_sink) +serd_writer_new(SerdWorld* world, + SerdSyntax syntax, + SerdWriterFlags flags, + const SerdEnv* env, + SerdOutputStream* output, + size_t block_size) { assert(world); assert(env); - assert(byte_sink); + assert(output); + + SerdBlockDumper dumper = {NULL, NULL, 0u, 0u}; + if (serd_block_dumper_open(&dumper, output, block_size)) { + return NULL; + } const WriteContext context = WRITE_CONTEXT_NULL; SerdWriter* writer = (SerdWriter*)calloc(1, sizeof(SerdWriter)); @@ -1332,7 +1342,7 @@ serd_writer_new(SerdWorld* world, writer->env = env; writer->root_node = NULL; writer->root_uri = SERD_URI_NULL; - writer->byte_sink = byte_sink; + writer->output = dumper; writer->context = context; writer->empty = true; @@ -1425,6 +1435,7 @@ serd_writer_free(SerdWriter* writer) } serd_writer_finish(writer); + serd_block_dumper_close(&writer->output); free(writer->anon_stack); serd_node_free(writer->root_node); free(writer); -- cgit v1.2.1