Нечего сказать? Считаете окружающих недостаточно достойными, чтобы разжевывать азы?
Значит все таки хотите, что бы я АЗЫ разжевал.
Дело в том что в цепочке обработчиков общую скорость определяет самый медленный. Быстрее него вся цепочка работать не сможет. 
А у нас такой случай сначала генерируются данные потом записываются в файл.
А если взять средства для замера производительности, то видно что самый медленный обработчик это генерация элемента матрицы.
Т.е. это наш случай, когда не стоит заниматься оптимизацией работы с файлами, а стоит заняться оптимизацией генерации данных.
Как бы вы не оптимизировали работу с файлами, но пока данные генерируются 2 часа.  Программа будет работать 2 часа.
Пока мы не с генерируем порцию данные диск будет ждать.
Поэтому я и оптимизировал работу со строками время снизилось до 20 минут. Инструмент показал что операции стали соизмеримы. У  уже только тогда я сделал оптимизацию записи.
Время снизилось  до 10 минут. Но всё равно, диск работает 10% от своей скорости, так как вынужден ждать процессор.
И надо дальше оптимизировать генерацию данных.  Но так как это модель и точный алгоритм генерации будет отличаться, то остановился на этом результате.
Код:
type
 TIndexRec=packed record
    FilePos:Int64;
    Size:Int64;
    i,j:Integer;
    end;
 TAIndexRec=array [0..0] of TIndexRec;
 PAIndexRec=^TAIndexRec;
 PIndexRec=^TIndexRec;
type
  TFastString=record
    Size:Integer;
    str:String;
    end;
procedure Add(var fs:TFastString; const s:String); overload;
var LenS:DWord;
begin
LenS:=Length(s);
if fs.Size+LenS>Length(fs.str) then
   begin
   SetLength(fs.str,Length(fs.str)*2);
   end;
move(s[1],fs.str[fs.Size+1],LenS);
fs.Size:=fs.Size+LenS;
end;
procedure FastStringInit(var fs:TFastString; Size:Integer);
begin
fs.Size:=0;
SetLength(fs.str,Size);
end;
function GenStr(i:Integer):String;
var
 k:Integer;
 s0:String;
 fs:TFastString;
begin
        result := '';
        FastStringInit(fs,1000*15);
        for k:=1 to 1000 do
         begin
         s0 := intToStr(k*i); // имитируем вычисления элемента (i, j)
         Add(fs,s0);
         s0:=', ';
         Add(fs,s0)
         end;
        result:=fs.str;
        SetLength(result,Fs.Size);
end;
function IndexRec(FilePos:Int64; Size:Int64; i,j:Integer):TIndexRec;
begin
        Result.FilePos:=FilePos;
        Result.Size:=Size;
        Result.i:=i;
        Result.j:=j;
end;
procedure TForm1.Button2Click(Sender: TObject);
const
 nm = 1000; // параметр масштаба
var
 i,j : integer;
 fp:Int64;
 s,path:String;
 fs:TFastString;
 bdName,iName : string;
 fbd,fi : File of byte;
 t0,t1 : TDateTime;
 IndexCashe:array [1..1000] of TIndexRec;
 Size:Integer;
begin
  path := extractFilePath (application.ExeName)+'tmp';
  if not DirectoryExists(path) then
     MkDir (path); 
  bdName:=path+'\Matrix.bd';
  iName:=path+'\Matrix.i';
  AssignFile(fbd,bdName);
  AssignFile(fi,iName);
  Rewrite(fbd);
  Rewrite(fi);
  t0 := now;      // стартовое время
  fp:=0;
  FastStringInit(fs,1024*1024);
  for i:=1 to nm do  // для строк i
   begin
    for j:=1 to 1000 do // для столбца j
     begin
        s:=GenStr(i);
        Add(fs,s);
        IndexCashe[j]:=IndexRec(fp,Length(s),i,j);
        Inc(fp,Length(s));
        if fs.Size>$100000 then  // 1 МБайт
           begin
           BlockWrite(fbd,fs.str[1],fs.Size*SizeOf(fs.str[1]));
           fs.Size:=0;
           end;
     end;
     BlockWrite(fi,IndexCashe[1],Length(IndexCashe)*SizeOf(IndexCashe[1]));
     Caption := Format('%d',[i]); // Отображаем прогресс
    end;
  BlockWrite(fbd,fs.str[1],fs.Size*SizeOf(fs.str[1]));
  closeFile(fbd); 
  closeFile(fi); 
  t1 := now; 
  Button2.Caption := 'Done';
  Label1.Caption := ('test finished '+ FloatToStr(SecondSpan(t1,t0))+' sec.');
end;
Что касается сортировки, то я уже описал принципы и закодировал согласно им. Время работы составляет пару минут и упирается в жёсткий диск. Далее только диск менять или весь компьютер. Код не привожу, так как ещё надо доработать, протестировать и оформить должным образом.
Цитата:
И еще, я тут вспомнил, эффективнее всего писать данные блоками, равными блоку файловой системы.
Кратность блока может быть произвольным и почти не влияет на производительность.  Т.е он может быть и не кратным.
Кратность была важна 20-40 лет назад, когда памяти было мало. А сейчас ОС кэширует данные и не тратиться на дополнительно чтение. 
Вот если вы свой драйвер для жесткого диска пишете. о вам стоит подумать о кэширование. Просто по привычке используют степень кратную 2.
Цитата:
Ну и вообще имеет смысл погуглить что-нибудь про WinAPI и оптимизацию
Не имеет смысла. При больших блоках порядка 256,512 КБайт Write выдаёт 99% от скорости работы жёсткого диска. И доведя процент до 100% вы получите прирост 1.01 раза. Что соизмеримо с погрешностью измерения работы всей системы.
Вот если бы Write выдавал 50% тогда стоило задумываться. 
Нет смысла оптимизировать скорость на десятки процентов. Так как эту разницу легко решить заменой комплектующих. А вот если надо ускориться в разы то тут стоит смотреть в сторону алгоритмов, а не WinAPI.  Так как переход от O(n^2) к  O(n) даст существенный выигрыш. Есть конечно трюки для ускорения в том числе и в разы.
В примере я отказался от Write в пользу BlockWrite только из за того что мне так захотелось, не более того.