Ну, если никому тут не интересно обсуждать общие приёмы алгоритмической оптимизации, жаль...
Сейчас я ещё подускорю свой код с помощью решета Эратосфена, но надо пояснить, что далеко не всякая оптимизация мне вообще нужна.
Сейчас нет смысла заниматься какой-то мало-мальски низкоуровневой оптимизацией, потому что мне хочется понять что-то в математике, чтобы не было “за деревьями не видно леса”. Пример: если мы считаем P(1000), то есть вероятность того что два случайных числа от 1 до 1000 не имеют общего делителя, то это у меня делается перебором в двух вложенных циклах, и ясно что можно ускорить этот перебор в два раза, начиная второй счётчик не с 1 а с первого счётчика (использовать знание, что факт того что A и B не имеют общего делителя совпадает с аналогичным фактом что B и A не имеют общего делителя). Но такая оптимизация дополнительно усложняет код, соответственно увеличивает вероятность ошибок в чём-то более важном.
Мой код ужасно медленный просто потому, что там многократно дублируются вычисления. Если код считает сначала P(100), потом P(200), потом P(300) и так далее, то на тысячной итерации, т.е. когда доходит до P(100000), все предыдущие итерации оказываются ненужными, и суммарно график строится почти в тысячу раз медленнее, чем если сразу посчитать P(100000). Можно это ускорить за счёт того, что когда мы посчитали P(99900) и далее считаем P(100000), можно перебирать только пары чисел от 99901 до 100000, и далее правильно всё просуммировать. Но зачем мне это делать, если это увеличивает сложность кода и соответственно вероятность ошибок?
Поэтому я сейчас свой код даже замедлил: написал функцию CalcPNonCoprime(N:integer), которая каждый раз инициирует и заполняет все массивы. Пусть она запускается 10 раз; за 22 минуты мой код с предыдущим алгоритмом дал такой график:

Сейчас я его ускорю решетом Эратосфена (или может это как-то ещё называется).