/*
* Copyright 2015 Florian Philipp
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <wchar.h>
/* using wcsrtombs, wcrtomb, wcstrlen */
#include <stdlib.h>
/* using malloc, realloc, free */
#include <string.h>
/* using memset, memcpy */
#include <stdio.h>
/* using printf, perror */
#include <locale.h>
/* using setlocale */
#include <errno.h>
/* using errno */
#include <time.h>
/* using clock_gettime, difftime */
/**
* Utility to preserve errno when calling free
*/
static void _free_keep_errno(void* buf)
{
int tmp_errno;
tmp_errno = errno;
free(buf);
errno = tmp_errno;
}
/**
* Converts a wide character string to a multi-byte character string
*
* Operates by measuring the size and allocating the resulting string once
*
* \param ws a null-terminated wide character string
* \return the corresponding multi-byte character string or NULL on error
*/
static char* s_wstring_to_string_meas(const wchar_t* ws)
{
const wchar_t* count_ws;
mbstate_t mbstate;
size_t len;
char* buf;
count_ws = ws;
memset(&mbstate, 0, sizeof(mbstate));
if((len = wcsrtombs(NULL, &count_ws, 0, &mbstate)) == (size_t) -1)
goto err_rtrn;
len += 1;
if(! (buf = malloc(len)))
goto err_rtrn;
if(wcsrtombs(buf, &ws, len, &mbstate) == (size_t) -1)
goto err_free;
return buf;
err_free:
_free_keep_errno(buf);
err_rtrn:
return NULL;
}
/**
* Converts a wide character string to a multi-byte character string
*
* Operates by allocating enough memory if each wchar_t translates into
* MB_CUR_MAX bytes, then shrinking the string in the end
*
* \param ws a null-terminated wide character string
* \return the corresponding multi-byte character string or NULL on error
*/
static char* s_wstring_to_string_max(const wchar_t* ws)
{
size_t len;
char* buf, *shrunk;
mbstate_t mbstate;
/* make a worst case estimate. Note that MB_CUR_MAX is a runtime variable */
len = MB_CUR_MAX * wcslen(ws) + 1;
if(! (buf = malloc(len)))
goto err_rtrn;
memset(&mbstate, 0, sizeof(mbstate));
if((len = wcsrtombs(buf, &ws, len, &mbstate)) == (size_t) -1)
goto err_free;
if(! (shrunk = realloc(buf, len + 1)))
goto err_free;
return shrunk;
err_free:
_free_keep_errno(buf);
err_rtrn:
return NULL;
}
/**
* Converts a wide character string to a multi-byte character string
*
* Helper function for different high-level implementations.
* Uses an initial guess for the size.
* If the guessed size is insufficient, larger memory is allocated
* exponentially.
*
* \param ws a null-terminated wide character string
* \param buflen initial size estimate
* \return the corresponding multi-byte character string or NULL on error
*/
static char* _s_wstring_to_string_realloc(const wchar_t* ws, size_t buflen)
{
char* buffer;
char* resized;
size_t bufpos ;
mbstate_t mbstate;
memset(&mbstate, 0, sizeof(mbstate));
/* to avoid parsing the string twice, we allocate memory speculatively
* with exponential growth. Then we shrink it at the end
*/
for(buffer = NULL, bufpos = 0; ws; buflen *= 2) {
size_t converted;
if(! (resized = realloc(buffer, buflen)))
goto err;
buffer = resized;
if((converted = wcsrtombs(buffer + bufpos, &ws, buflen - bufpos, &mbstate))
== (size_t) -1)
goto err;
bufpos += converted;
}
/* shrink buffer to actually required size */
if(! (resized = realloc(buffer, bufpos + 1)))
goto err;
return resized;
err:
_free_keep_errno(buffer); /* buffer may be NULL */
return NULL;
}
/**
* Converts a wide character string to a multi-byte character string
*
* Operates by allocating the one byte per wide character and then extending it
* exponentially
*
* \param ws a null-terminated wide character string
* \return the corresponding multi-byte character string or NULL on error
*/
static char* s_wstring_to_string_min(const wchar_t* ws)
{
return _s_wstring_to_string_realloc(ws, wcslen(ws) + 1);
}
/**
* Converts a wide character string to a multi-byte character string
*
* Operates by making an uneducated guess on the memory size and then extending
* it exponentially.
* Currently, the initial guess is 64, i.e. one cache line
*
* \param ws a null-terminated wide character string
* \return the corresponding multi-byte character string or NULL on error
*/
static char* s_wstring_to_string_guess(const wchar_t* ws)
{
return _s_wstring_to_string_realloc(ws, 64);
}
/**
* Converts a wide character string to a multi-byte character string
*
* Operates by allocating exponentially larger strings. Then concatenates them
* in the end
*
* \param ws a null-terminated wide character string
* \return the corresponding multi-byte character string or NULL on error
*/
static char* s_wstring_to_string_rope(const wchar_t* ws)
{
struct _String
{
size_t len;
char* str;
};
/* Array of exponentially growing strings */
struct _String* rope;
size_t ropelen = 0, ropecap = 64 / sizeof(struct _String);
/* allocation size. Initial size is a cache line */
size_t lastreserved = 64;
size_t i;
size_t outlen = 1;
char* outbuf;
char* outpos;
int errno_cpy;
mbstate_t mbstate;
memset(&mbstate, 0, sizeof(mbstate));
if(! (rope = malloc(ropecap * sizeof(struct _String))))
goto err_rtrn;
/* Convert the string into increasingly large substrings */
for(i = 0; ws; ++i, lastreserved *= 2) {
if(i == ropecap) {
struct _String* newrope;
ropecap *= 2;
if(! (newrope = realloc(rope, ropecap * sizeof(struct _String))))
goto err_free;
rope = newrope;
}
if(! (rope[i].str = malloc(lastreserved)))
goto err_free;
ropelen = i + 1;
if((rope[i].len = wcsrtombs(rope[i].str, &ws, lastreserved, &mbstate))
== (size_t) -1)
goto err_free;
outlen += rope[i].len;
};
/* allocate the final string. reallocing the first strings saves us the
* hassle of copying its content and freeing it.
* It also deals with the common case that the first string was big enough
* and just needs to be shrunk
*/
if(! (outbuf = realloc(rope[0].str, outlen)))
goto err_free;
/* append the remaining strings */
outpos = outbuf + rope[0].len;
for(i = 1; i < ropelen; ++i) {
outpos = memcpy(outpos, rope[i].str, rope[i].len) + rope[i].len;
free(rope[i].str);
}
*outpos = '\0';
free(rope);
return outbuf;
err_free:
/* free all entries. The last entry may be NULL if allocation failed.
* free may call munmap so we preserve errno before calling it
*/
errno_cpy = errno;
for(i = 0; i < ropelen; ++i)
free(rope[i].str);
free(rope);
errno = errno_cpy;
err_rtrn:
return NULL;
}
/**
* Creates n random valid wchar_ts
*/
static void make_wchars(wchar_t* ws, size_t n)
{
mbstate_t lastvalid, cur;
size_t i;
memset(&lastvalid, 0, sizeof(lastvalid));
for(i = 0; i < n; ++i) {
char buf[MB_CUR_MAX];
do {
memcpy(&cur, &lastvalid, sizeof(cur));
ws[i] = (wchar_t) (rand() + 1);
}
while(wcrtomb(buf, ws[i], &cur) == (size_t) -1);
memcpy(&lastvalid, &cur, sizeof(lastvalid));
}
}
int main(void)
{
typedef char* (*algorithm_t)(const wchar_t*);
static const algorithm_t algorithms[] = {
s_wstring_to_string_meas,
s_wstring_to_string_max,
s_wstring_to_string_min,
s_wstring_to_string_guess,
s_wstring_to_string_rope
};
static const char* const names[] = {
"measure", "max", "min", "guess", "rope"
};
const size_t len = 4096, algo_n = sizeof(names) / sizeof(names[0]);
wchar_t* wstr;
size_t curlen;
const char* operation;
setlocale(LC_ALL, "");
if(! (wstr = malloc(len * sizeof(wchar_t)))) {
operation = "Allocating wide characters";
goto err_rtrn;
}
make_wchars(wstr, len - 1);
wstr[len - 1] = L'\0';
for(curlen = 1; curlen <= len; curlen *= 2) {
const wchar_t* cur_wstr;
size_t repetitions;
size_t algo_i;
cur_wstr = wstr + len - curlen;
repetitions = 4096 * 1024 * 8 / curlen;
for(algo_i = 0; algo_i < algo_n; ++algo_i) {
struct timespec starttime, endtime;
size_t repetition_i;
double spenttime, time_per_wchar;
if(clock_gettime(CLOCK_MONOTONIC, &starttime)) {
operation = "Getting time";
goto err_free;
}
for(repetition_i = 0; repetition_i < repetitions; ++repetition_i) {
char* mbstr;
if(! (mbstr = algorithms[algo_i](cur_wstr))) {
operation = names[algo_i];
goto err_free;
}
free(mbstr);
}
if(clock_gettime(CLOCK_MONOTONIC, &endtime)) {
operation = "Getting time";
goto err_free;
}
spenttime = difftime(endtime.tv_sec, starttime.tv_sec)
+ (endtime.tv_nsec - starttime.tv_nsec) * 1e-9;
time_per_wchar = spenttime / (repetitions * curlen);
if(printf("%zu\t%s\t%g\n", curlen, names[algo_i], time_per_wchar) < 0) {
operation = "Printing results";
goto err_free;
}
}
}
free(wstr);
return 0;
err_free:
_free_keep_errno(wstr);
err_rtrn:
perror(operation);
return 1;
}
Comments