I would think that substr would be fast enough to do what you need. The main bottleneck, as BrowserUK said, is probably not substr, but rather disk read (if you're reading one record at a time) or memory allocation. On the former, you could try using read() and a buffer variable. Be simple enough to read in some large multiple of 10 (9 + separator character) and then generate directly from that:
use strict;
use warnings;
my ($handle, $buffer, $size, @parsed, $i);
open($handle, 'my-data.txt');
while ($size = read($handle, $buffer, 30)) {
for ($i = 0; $i < $size; $i += 10) {
push @parsed, [
substr($buffer, $i, 3),
substr($buffer, $i+3, 3),
substr($buffer, $i+6, 3)
];
}
}