0

I'm making ASCII game and I need performance, so decided to go with printf(). But there is a problem, I designed my char array as multidimensional char ** array, and printing it outputs garbage of memory instead of data. I know it's possible to print it with a for loop but the performance rapidly drops that way. I need to printf it like a static array[][]. Is there a way?

I did some example of working and notWorking array. I only need printf() to work with nonWorking array.

edit: using Visual Studio 2015 on Win 10, and yeah, I tested performance and cout is much slower than printf (but I don't really know why is this happening)

#include <iostream>
#include <cstdio>

int main()
{
    const int X_SIZE = 40;
    const int Y_SIZE = 20;

    char works[Y_SIZE][X_SIZE];
    char ** notWorking;

    notWorking = new char*[Y_SIZE];
    for (int i = 0; i < Y_SIZE; i++) {
        notWorking[i] = new char[X_SIZE];
    }

    for (int i = 0; i < Y_SIZE; i++) {
        for (int j = 0; j < X_SIZE; j++) {
            works[i][j] = '#';
            notWorking[i][j] = '#';
        }
        works[i][X_SIZE-1] = '\n';
        notWorking[i][X_SIZE - 1] = '\n';
    }
    works[Y_SIZE-1][X_SIZE-1] = '\0';
    notWorking[Y_SIZE-1][X_SIZE-1] = '\0';

    printf("%s\n\n", works);
    printf("%s\n\n", notWorking);

    system("PAUSE");
}

Note: I think I could make some kind of a buffer or static array for just copying and displaying data, but I wonder if that can be done without it.

Shabrido
  • 79
  • 1
  • 6
  • printf("%s\n%s\n%s\n%s\n [...]", notWorking[0], notWorking[1], notWorking[2], notWorking[3], [....]); would do it (assuming you NUL-terminate each allocated string, of course), but I doubt it will be any faster... it just means that you're using a for loop that is inside the printf() implementation rather than your own. Plus it would be really ugly and hard to maintain. – Jeremy Friesner Oct 22 '15 at 14:16
  • When you use `%s` in `printf` format string, what you pass will be casted to a const char * pointer. The reason that `working` works is that it can be interpretted as a single string successfully, without UB. Notworking cannot work and is UB, there is no way that printf can handle a complex data structure like that. You need to use a loop or something. – Chris Beck Oct 22 '15 at 14:17
  • 1
    Btw have you actually measured a drop in performance? Because in my experience the time taken by the for loop itself will be negligible compared to the time taken by the output I/O (which will be the same in either case, since you're outputting the same characters in either case) – Jeremy Friesner Oct 22 '15 at 14:17
  • Another thing to keep in mind: `printf` is not some kind of magic, it internally uses loops also. – Chris Beck Oct 22 '15 at 14:19
  • If only there were a statically typed way of printing text that would safely allow you to overload your own stream operators, alas. Seriously though, your aversion to using `cout` is probably misplaced, are you sure you are [unsynching](http://en.cppreference.com/w/cpp/io/ios_base/sync_with_stdio) the streams, not flushing unnecessarily with `endl`, and compiling with optimizations turned on? – user657267 Oct 22 '15 at 14:21
  • Yes, I tested all performance issues and I was shocked how quicker printf() was compared to cout <<. Or maybe cout is so slow or there is a secret way to speed it up. edit: Using Visual Studio 2015 – Shabrido Oct 22 '15 at 14:23
  • Your problem is that the `notWorking[i]`'s are not contiguous in memory. **EDIT** see @dasblinkenlight's answer. – maddouri Oct 22 '15 at 14:26
  • "I need performance, so decided to go with printf()" This is so very wrong. printf is perhaps the slowest function of all in standard C. If you want speed and portability is not important, consider using system-specific API instead. – Lundin Oct 22 '15 at 14:27
  • @Shabrido just about every time somebody brings this up it's because they're not doing one of the 3 things I mentioned. – user657267 Oct 22 '15 at 14:28
  • You are looking for performance in all the wrong places. Cout is slower than printf (ask me why), but it's fast enough for your game. Ditto std::string vs char pointers. – n. m. could be an AI Oct 22 '15 at 14:33
  • @n.m. It's not, it constantly causes screen flickering while printf is not doing it. While printing massive strings printf is about 2-3 time faster as I tested. But that's only my opinion and I could be simply wrong. – Shabrido Oct 22 '15 at 14:36
  • By the way.. In my game I need to instantly print all battlefield with hundreds of units (ASCII chars ofc) And then it's really simple to see performance differences. – Shabrido Oct 22 '15 at 14:39
  • 2-3 times faster is about the right figure if you don't unsync with stdio. After you unsync it becomes nearly indistinguishable (in my tests). But what do I know. It only outputs 10M or 30M characters per seconds on my laptop when the output goes to a file. Naturally the console can't possibly keep up with such speed, so your output becomes bound by the console speed rather than printf or iostreams speed. – n. m. could be an AI Oct 22 '15 at 14:53

2 Answers2

1

If you would like to print a 2D structure with printf without a loop, you need to present it to printf as a contiguous one-dimension C string. Since your game needs access to the string as a 2D structure, you could make an array of pointers into this flat structure that would look like this:

Array and Buffer

Array of pointers partitions the buffer for use as a 2D structure, while the buffer itself can be printed by printf because it is a contiguous C string.

Here is the same structure in code:

// X_SIZE+1 is for '\n's; overall +1 is for '\0'
char buffer[Y_SIZE*(X_SIZE+1)+1];
char *array[Y_SIZE];
// Setup the buffer and the array
for (int r = 0 ; r != Y_SIZE ; r++) {
    array[r] = &buffer[r*(X_SIZE+1)];
    for (int c = 0 ; c != X_SIZE ; c++) {
        array[r][c] = '#';
    }
    array[r][X_SIZE] = '\n';
}
buffer[Y_SIZE*(X_SIZE+1)] = '\0';
printf("%s\n", buffer);

Demo.

Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
  • Yeah.. I'll probably go with that, or I'll simply convert my design to one dimensional array to save myself all that trouble. – Shabrido Oct 22 '15 at 14:41
  • @Shabrido There's no point to convert everything to 1D if it's more convenient for your code to deal with it in 2D. Since you are in C++, you could build a class that presents a 1D array as 2D, too, and computes pointers on the fly. However, since the number of pointers is going to be relatively small, you may save some CPU cycles by pre-computing and storing them. – Sergey Kalinichenko Oct 22 '15 at 14:44
  • Why not simply allocate a true 2D array? I see no reason to have a "mangled" 1D array, nor a pointer-to-pointer table. – Lundin Oct 22 '15 at 14:47
  • @Lundin Array of pointers preserves the flexibility of the OP's array of pointers, in case he wants to switch to a non-rectangular field at some point. Having an array of pointers or computing pointers yourself lets you deal with the field as if it were a 2D structure, but allows for, say, a triangular field. – Sergey Kalinichenko Oct 22 '15 at 15:04
1

Some things you can do to increase performance:

  • There is absolutely no reason to have an array of pointers, each pointing at an array. This will cause heap fragmentation as your data will end up all over the heap. Allocating memory in adjacent cells have many benefits in terms of speed, for example it might improve the use of data cache.

    Instead, allocate a true 2D array:

    char (*array2D) [Y] = new char [X][Y];
    
  • printf as well as cout are both incredibly slow, as they come with tons of overhead and extra features which you don't need. Since they are just advanced wrappers around the system-specific console functions, you should consider using the system-specific functions directly. For example, the Windows console API. It will however turn your program non-portable.

    If that's not an option, you could try to use puts instead of printf, since it has far less overhead.

  • Main performance issue with printf/cout is that they write to the end of the "standard output stream", meaning you can't write where you like, but always at the bottom of the screen. Forcing you to constantly redraw the whole thing every time you changed something, which will be slow and possibly cause flicker issues.

    Old DOS/Turbo C programs solved this with a non-standard function called gotoxy which allowed you to move the "cursor" and print where you liked. In modern programming, you can do this with the console API functions. Example for Windows.

  • You could/should separate graphics from the rest of the program. If you have one thread handing graphics only and the main thread handling algorithms, the graphic updates will work smoother, without having to wait for whatever else the program is doing. It makes the program far more advanced though, as you have to consider thread safety issues.

Community
  • 1
  • 1
Lundin
  • 195,001
  • 40
  • 254
  • 396
  • Thanks.. I had no idea about many of this things, I'm still learning and asking question all the time. – Shabrido Oct 22 '15 at 14:48
  • 1
    @Shabrido Regarding the heap fragmentation, that is wide-spread but bad practice inherited from C style programs. Multiple crappy C books preach using such heap fragmentation tables. It is actually possible that std::vector is faster than your pointer-to-pointer thing, because it might internally have a sound way of allocating dynamic data. Though of course std::vector comes with a ton of other, unrelated overhead, so it will be slower for other reasons. – Lundin Oct 22 '15 at 14:57
  • to be honest I've never seen this syntax of creating what you call "true 2D array" – Shabrido Oct 22 '15 at 14:59
  • And thank you for taking your time, every time I enter this site I feel enlightened! – Shabrido Oct 22 '15 at 15:01
  • 1
    @Shabrido It is an array pointer. It looks odd because I left out the inner-most dimension. More formally correct would be to write `char (*array) [X][Y]`, but then you would have to access the array items as `(*array)[i][j]` rather than `array[i][j]`, which quickly gets annoying and hard to read. – Lundin Oct 22 '15 at 15:01
  • @Shabrido Btw I just realized that I didn't mention the most problematic thing, which is that you can't print anywhere on the console with the standard functions. Updated by answer. – Lundin Oct 22 '15 at 15:06