0
\$\begingroup\$

I wrote a D implementation of the nul2pfb utility from here, as the link to the source code was broken and I wanted to try to learn D. I noticed that it was rather slow (could barely keep up with the find -print0 that was passing it data, when it should be far faster as it need not do anywhere near as many system calls).

This implementation works correctly (tested with zsh and bash printf built-ins, as well as /usr/bin/printf).

I am a newbie to D and do not understand.

How can I improve this code?

import std.stdio;
import std.conv;

void main()
{
  foreach (ubyte[] mybuff; chunks(stdin, 4096)) {
    encodebuff (mybuff);
  }
}

@safe void encodebuff (ubyte[] mybuff) {
  foreach (ubyte i; mybuff) {
    char b = to!char(i);
    switch (i) {
      case 'a': .. case 'z':
      case 'A': .. case 'Z':
      case '0': .. case '9':
      case '/':
      case '.':
      case '_':
      case ':': writeChar(b); break;
      default: writeOctal(b); break;
      case 0: writeChar ('\n'); break;
      case '\\': writeString(`\\`); break;
      case '\t': writeString(`\t`); break;
      case '\n': writeString(`\n`); break;
      case '\r': writeString(`\r`); break;
      case '\f': writeString(`\f`); break;
      case '\v': writeString(`\v`); break;
      case '\a': writeString(`\a`); break;
      case '\b': writeString(`\b`); break;
    }
  }
}

@trusted void writeString (string a)
{
  write (a);
}

@trusted void writeOctal (int a)
{
  writef ("\\%.4o", a); // leading 0 needed for for zsh printf '%b'
}

@trusted void writeChar (char a)
{
  write (a);
}

Tests: (note that filelist is a NUL-delimited list of files generated by find -print0 on my home directory, and filelist2.txt is generated from filelist by filelist.txt sed -e 's/\x0/\n/g' > filelist2.txt and is thus the corresponding list of newline-delimited filenames).

# the sed script escapes the backslashes so xargs does not clobber them
diff filelist2.txt <(<filelist.txt char2code2 | sed -e 's/\\/\\\\/g' | xargs /usr/bin/printf "%b\n") 
# from within zsh
bash -c 'diff filelist2.txt <(for i in "$(<filelist.txt char2code)"; do printf "%b\n" "$i"; done)' 
# from within zsh and bash
diff filelist.txt <(for i in $(char2code <filelist.txt); do printf '%b\0' "$i"; done)
# from within zsh, bash, and dash
for i in $(char2code <filelist.txt); do printf '%b\0' "$i"; done | diff - filelist.txt
\$\endgroup\$
2
\$\begingroup\$

Do write and writef automatically buffer writes? If not I bet that's your problem.

One suggestion I do have that you could try is using a final switch statement. It would require an enum containing all the possible values of a ubyte, but in return your switch would essentially be a jump table and a bit faster.
If you're worried about this making your switch look huge you could just mixin the boring portion, generating a string within a loop and adding that string to compilation.

You could also try using a char[128] to store all conversions from ubyte to char, skipping the to! call which might be a bit slower. Making the conversion an array lookup instead of a method call.

This code doesn't use the buffered writes I mentioned, I'll leave that to someone else since I'm unsure about it.

import std.stdio;
import std.conv;

//This enum is so you can use final switch, use a proper ascii order
enum MyEnum { 
    J0, J1, J2, ... //Using J because you can't start identifiers with a number, so I just picked something
    Ja, Jb, Jc, ...
    JA, ...
    ...
}

//a lookup table to make conversion possible in this direction.
static MyEnum[128] reverseMyEnum = {
    J0, J1, J2, ... //in proper ascii order, I know this is wrong
}

//replaces your use of to!
static char[128] ascii = {
    //just put the ascii chars here
}

void main()
{
  foreach (ubyte[] mybuff; chunks(stdin, 4096)) {
    encodebuff (mybuff);
  }
}

@pure string generateBoring() {
    string code;
    for (char value; a .. z, A .. Z, 0 .. 9)
        code ~= "case MyEnum.J'"~value~"': writeChar(b); break;\n";
    for (char value; any other uncaught cases)
        code ~= "case MyEnum.J'"~value~"': writeOctal(b); break;\n";
    return code;
}

@safe void encodebuff (ubyte[] mybuff) {
  foreach (ubyte i; mybuff) {
    char b = ascii[i]; //integer promotion lets you use a ubyte where int is expected
    final switch (reverseJB[i]) {
        mixin(generateBoring());
        case 0: writeChar ('\n'); break;
        case MyEnum.JBackBack: writeString(`\\`); break;
        case MyEnum.JBackT: writeString(`\t`); break;
        case MyEnum.JBackN: writeString(`\n`); break;
        case MyEnum.JBackR: writeString(`\r`); break;
        case MyEnum.JBackF: writeString(`\f`); break;
        case MyEnum.JBackV: writeString(`\v`); break;
        case MyEnum.JBackA: writeString(`\a`); break;
        case MyEnum.JBackB: writeString(`\b`); break;
    }
  }
}

@trusted void writeString (string a)
{
  write (a);
}

@trusted void writeOctal (int a)
{
  writef ("\\%.4o", a); // leading 0 needed for for zsh printf '%b'
}

@trusted void writeChar (char a)
{
  write (a);
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.