The task:
A set of functions used to read/write integers from/to stdin/stdout as efficiently as possible.
Rationale:
printf
/scanf
fail to offer best possible performance. For example, it is possible to write a solution for UVA problem 10055 "Hashmat the Brave Warrior" that runs in below 0.01 sec, however a straightforward solution with printf
/ scanf
typically runs in around 0.02 sec. Digit by digit input/output of numbers using getchar_unlocked
and putchar_unlocked
is the only way I'm aware of to make it run as fast as possible.
Result:
- Functions
rd*
read integers, and functionswr*
write integers. - The next few letters in the function's name describe the type the function operates on, with similar naming conventions to
printf
/scanf
format specifiers. So for examplerdllu
reads anunsigned long long int
, whilewrhhi
writes the numerical value of asigned char
. (It is possible to use a function operating on unsigned integers to read / write a signed integer, this will simply skip checking if the integer is negative). - In addition, every
rd*
function has a counterpart ending witheof
, meaning that it checks for EndOfFile and returns0
in that case. Functions not checking for EOF always return1
. This is to enable convenient reading a few integers in a loop likewhile(rdllueof(&a) && rdllu(&b)) {do_something;}
. - You might say that these names are cryptic and unreadable. Honestly though, I think they're not much worse than standard library functions like
strncmpy
. And definitely much better than the alternative, that isread_unsigned_long_long_int_while_checking_for_eof
. If I am to avoid such long names I'm not sure what else can I devise.
Approach:
Sorry for using macros to generate these functions, that's another point I suppose you might think is wrong. But otherwise I'd have to write 30 repetitive functions, which would violate DRY pretty hard.
The code:
#include <stdio.h>
int fisdigit(int ch)
{
return ch >= '0' && ch <= '9';
}
#define rdi_funcname_sign_1_eof_1(abbrev) rd##abbrev##ieof
#define rdi_funcname_sign_1_eof_0(abbrev) rd##abbrev##i
#define rdi_funcname_sign_0_eof_1(abbrev) rd##abbrev##ueof
#define rdi_funcname_sign_0_eof_0(abbrev) rd##abbrev##u
#define wri_funcname_sign_1(abbrev) wr##abbrev##i
#define wri_funcname_sign_0(abbrev) wr##abbrev##u
#define rwi_argtype_sign_1(type) unsigned type
#define rwi_argtype_sign_0(type) signed type
#define read_integer(type, abbrev, check_sign, check_eof) \
int \
rdi_funcname_sign_##check_sign##_eof_##check_eof(abbrev) \
(rwi_argtype_sign_##check_sign(type)* n) \
{ \
*n = 0; \
int ch; \
int is_neg = 0; \
\
do { \
ch = getchar_unlocked(); \
if(check_eof) { \
if(ch == EOF) \
return 0; \
} \
if(check_sign) { \
if(ch == '-') \
is_neg = 1; \
}\
} while(!fisdigit(ch)); \
\
do { \
*n *= 10; \
*n += ch-'0'; \
ch = getchar_unlocked(); \
} while(fisdigit(ch)); \
if(check_sign) { \
if(is_neg) \
*n *= -1; \
} \
return 1; \
}
#define write_integer(type, abbrev, check_sign, max_digits) \
void \
wri_funcname_sign_##check_sign(abbrev) \
(rwi_argtype_sign_##check_sign(type) n) \
{ \
size_t const buffsiz = \
(check_sign ? max_digits+1 : max_digits); \
char buff[max_digits]; \
\
size_t i = 0; \
if(check_sign) { \
if(n < 0) \
buff[i++] = '-'; \
} \
while(n != 0) { \
buff[i++] = n%10; \
n /= 10; \
} \
\
if(i == 0) \
putchar_unlocked('0'); \
else \
while(i-- != 0) \
putchar_unlocked(buff[i] + '0'); \
\
return; \
}
#define rwi_variations(type, abbrev, max_digits_i, max_digits_u) \
read_integer(type, abbrev, 1, 1) \
read_integer(type, abbrev, 1, 0) \
read_integer(type, abbrev, 0, 1) \
read_integer(type, abbrev, 0, 0) \
write_integer(type, abbrev, 1, max_digits_i) \
write_integer(type, abbrev, 0, max_digits_u)
rwi_variations(long long int, ll, 19, 20)
rwi_variations(long int, l, 10, 10)
rwi_variations(int, , 5, 5)
rwi_variations(short int, h, 5, 5)
rwi_variations(char, hh, 3, 3)
#undef rdi_funcname_sign_1_eof_1
#undef rdi_funcname_sign_0_eof_1
#undef rdi_funcname_sign_1_eof_0
#undef rdi_funcname_sign_0_eof_0
#undef wri_funcname_sign_1
#undef wri_funcname_sign_0
#undef rwi_argtype_sign_1
#undef rwi_argtype_sign_0
#undef rwi_variations
#undef read_integer
#undef write_integer
Restriction:
Must compile with ANSI C 5.3.0 - GNU C Compiler with options: -lm -lcrypt -O2 -pipe -ansi -DONLINE_JUDGE
Use case:
The solution for the aforementioned UVA Problem 10055: "Hashmat the Brave Warrior":
void wrc(char c)
{
putchar_unlocked(c);
}
int main()
{
unsigned long long int a, b;
while(rdllueof(&a) && rdllu(&b)) {
if(a > b)
wrllu(a-b);
else
wrllu(b-a);
wrc('\n');
}
return 0;
}
I added the wrc
function just for consistency with the naming convention of wrllu
et al. I think it's better to print everything with wr*
rather than print integers with wr*
and characters with putchar_unlocked
.
PS
Any way to optimize this even more?
2 Answers 2
You worry about violating DRY, but you've violated a C standard, macros should always be all capitals.
While I don't recommend it, the fastest code would be to avoid the use of functions totally and just have the macros. Inline code should always be faster than functions, although on modern computers this is less of an issue.
The fisdigit()
function can be a macro or just inline
do {
...
} while(ch >= '0' && ch <= '9');
For performance reasons you don't want to call a function.
Check the ctype.h
file, isdigit()
may be a macro, in older versions of C (pre C89 for sure, possible pre C99 ) all functions provided by ctype.h
were macros.
Type Issues
The variables a
and b
in main are declared as unsigned long long, however the functions are expecting pointers to long long. It's also unclear that long long is required since 2^32 is the max value for either a
or b
.
-
\$\begingroup\$ Thank you for your answer! However: (a) It seems that
-O2
does optimize and inline function calls godbolt.org/g/Cyp1ej , so there seems to be no difference between macroifying or manually inliningfisdigit
and callingfisdigit
as a function. (b) If I didn't screw the macro code,rdllu
andrdllueof
should be expecting ptrs tounsigned long long
rather thanlong long
I have a vague feeling that i is permitted to read an integer through a pointer of a different singedness anyway, though I'm not 100% certain. \$\endgroup\$gaazkam– gaazkam2017年06月13日 15:08:05 +00:00Commented Jun 13, 2017 at 15:08 -
\$\begingroup\$ (c)
long long
is required, this is a peculiarity of this task. The problem is that max value for eithera
orb
is 2^32, while the max value ofunsigned long
is 2^32-1 , so we'd have an overflow in the corner case. \$\endgroup\$gaazkam– gaazkam2017年06月13日 15:09:27 +00:00Commented Jun 13, 2017 at 15:09 -
\$\begingroup\$ @gaazkam My compiler is giving me warnings about unsigned versus signed in the read functions. My comment about removing functions applied to all of the functions, both read and write. \$\endgroup\$2017年06月13日 15:13:02 +00:00Commented Jun 13, 2017 at 15:13
-
\$\begingroup\$ One final note: The real reason I chose to redefine
isdigit
: I thought I'd sooner or later expand this "library" to doubles, fixed-point, strings, etc, and that's where locale issues would kick in if I kept usingctype.h
functions, and I wanted to avoid this for the sake of simplicity and to avoid any overhead, so I thought for consistency I'd redefine everything in the first place. \$\endgroup\$gaazkam– gaazkam2017年06月13日 15:16:09 +00:00Commented Jun 13, 2017 at 15:16 -
1\$\begingroup\$ Thank you for noting the need of inlining all
rd*
andwr*
functions. It would seem that to achieve max performance they don't have to be explicitly macroified: instead we can get the same effect if we add the__inline__
keyword: gcc.gnu.org/onlinedocs/gcc-4.9.2/gcc/Inline.html And it does seem to be working: godbolt.org/g/586vSx \$\endgroup\$gaazkam– gaazkam2017年06月13日 15:54:44 +00:00Commented Jun 13, 2017 at 15:54
Bug: Below fails to print digits when
n<0
buff[i++] = n%10; ... putchar_unlocked(buff[i] + '0')
if(i == 0)
not needed, just use ado {} while
do { buff[i++] = n%10; } while (n /= 10);
Bug. Wrong array size
size_t const buffsiz = (check_sign ? max_digits+1 : max_digits); \ // char buff[max_digits]; char buff[buffsiz];
Why stingy on buffer size? Could simply use +1 always
size_t const buffsiz = max_digits+1; char buff[buffsiz];
Bug
buff[i++] = '-' ... putchar_unlocked(buff[i] + '0');
will not print a'-'
.
-
\$\begingroup\$ 1) You're right, thank you! 2) Indeed a neater construct, thank you! 3) You're right... I figured it out myself some time before, and even edited the question to remove this bug, but this edit got reverted... 4) Because why not? It's dispatched compile-time, so adds not runtime overhead! 5) Aaand you're right again. Have an upvote, thank you! \$\endgroup\$gaazkam– gaazkam2017年06月15日 09:08:18 +00:00Commented Jun 15, 2017 at 9:08
write_integer(type, abbrev, 1, max_digits_i) \ write_integer(type, abbrev, 0, max_digits_u)
. How does the receiving side separate the 2 integers as would they not be textually concatenated? \$\endgroup\$wrhhu(5); wrhhu(6);
but ratherwrhhu(5); wrc(' '); wrhhu(6)
Not sure why should it be any other way, C++iostream
facilities work in a similar way wrt this, ideone.com/XwFCnH \$\endgroup\$