#performance #f# #sequence
#Производительность #f# #последовательность
Вопрос:
В настоящее время я пытаюсь заняться программированием с помощью F #. Поэтому я написал простой скрипт F # для вычисления PI очень глупым способом. Я написал этот алгоритм на многих языках программирования, но на этот раз он выполняется довольно медленно, и я не знаю, что я делаю не так. По сравнению с моей версией Java это примерно в 10-20 раз медленнее (примерно 10-15 сек на моей машине, работающей в VS Code, нажав Alt Enter), что много (на мой взгляд).
Идея алгоритма состоит в том, чтобы бросать дротики в квадрат размером 1 на 1 с кругом в нем. Круг касается краев квадрата. Если дротик попадает в круг, он засчитывается. После того, как все дротики брошены, вы просто делите количество попаданий дротиков на общее количество дротиков и умножаете на 4, чтобы получить число ПИ.
Я перепробовал много способов получить мою ошибку, но не смог ее найти.
- Я попытался заменить генерацию случайных чисел фиксированными значениями -> не сделал многого
- Я попытался заменить математику.Sqrt с постоянным значением -> мало что сделал
- Я заменил вызовы Seq.xxx на массивы, надеясь, что это уменьшит накладные расходы. -> мало что сделал
PS: Я знаю, что этот метод вычисления числа Pi — отстой. Но это не то, что я пытаюсь здесь сделать.
open System
let random = Random(DateTime.Now.Millisecond)
let throwDart i = (random.NextDouble(), random.NextDouble())
let distance dart point = let (x1, y1), (x2, y2) = dart, point
(x2 - x1, y2 - y1)
let length distance = let (x, y) = distance
Math.Sqrt(x * x y * y)
let isInside circle dartHit =
let (center, radius) = circle
distance center dartHit |> length |> (fun x -> x < radius)
let circle = ((0.5, 0.5), 0.5)
let dartCount = 100000000
let dartSeqence = Seq.init dartCount throwDart
let pi = float(dartSeqence |> Seq.filter (isInside circle) |> Seq.length) / float(dartCount) * 4.0
Может быть, у кого-то есть идея, что я делаю не так. Я надеялся, что F # преуспеет в этой задаче, потому что это довольно простой и прямой алгоритм.
Заранее благодарю за помощь.
Обновление 1:
Привет, после повторного запуска моего Java-кода я был немного разочарован, потому что думал, что это быстрее. На моем компьютере этот код выполняется около 4,4 секунды:
import java.util.Random;
public class DartThrower{
Random random;
public DartThrower(){
random = new Random();
}
public boolean throwDart(){
double x = random.nextDouble();
double y = random.nextDouble();
// wenn Entfernung des Punktes vom Mittelpunkt (0.5|0.5) größer als 0.5 ist wird false ausgegeben
boolean inTheCircle = Math.sqrt((0.5 - x) * (0.5 - x) (0.5 - y) * (0.5 - y)) <= 0.5;
return inTheCircle;
}
}
class PiCalculator{
public static void main(String[] args){
long start = System.currentTimeMillis();
double hitCount = 0.0;
double throwCount = 100000000L;
DartThrower thrower = new DartThrower();
for (long i = 0; i < throwCount; i ){
boolean hit = thrower.throwDart();
hitCount = hit ? 1 : 0;
}
double a = hitCount / throwCount;
double pi = a * 4.0;
System.out.println("Pi= " pi);
System.out.println("took " (System.currentTimeMillis() - start) "ms");
}
}
Извините, но я думал, что время было меньше 1 секунды — вот почему я говорил о более быстром выполнении в 10-20 раз.
Это неправильно! После того, как я осознал, насколько я ошибался во времени, я решил добавить несколько временных меток для обоих и получил
- Java: около 4,3 с
- F #: прибл. 13.0с
Итак, фактор 3-4 между этими двумя. Эта разница может устареть, если я попытаюсь использовать обновления производительности @Tomas Petricek. Я попробую это и расскажу вам о результатах.
Пока спасибо за вашу помощь.
Комментарии:
1. Возможно, вы захотите создать консольный проект и попробовать сделать это в сборке выпуска. Вероятно, это будет скорее прямое сравнение.
2. Спасибо за идею. Я попробовал это с консольным приложением. Протестирована конфигурация «Release» и «Debug». Оба варианта такие же медленные, как и вариант VS Code: (
3. Можете ли вы показать свою реализацию Java?
4. @FlyingHubert Я думаю, что что-то не так — я получаю 15458 мс при отладке и 9469мс при сборке релиза. Они должны существенно отличаться.
5. В настоящее время я работаю и сегодня днем представлю реализацию Java.
Ответ №1:
Вы можете сделать это примерно в 2 раза быстрее, заменив отложенную последовательность простой изменяемой переменной и for
циклом (в этом нет ничего плохого в коде F #, критичном к производительности):
let mutable count = 0
for i in 0 .. dartCount do
if isInside circle (throwDart i) then count <- count 1
let pi = float count / float dartCount * 4.0
Вы можете добавить немного больше производительности (около 30% в моих грубых тестах), создав функции inline
, которые могут устранить некоторые блоки значений в виде кортежей:
let inline distance dart point =
let (x1, y1), (x2, y2) = dart, point
(x2 - x1, y2 - y1)
let inline length distance =
let (x, y) = distance
Math.Sqrt(x * x y * y)
let inline isInside circle dartHit =
let (center, radius) = circle
length (distance center dartHit) < radius
При этом я не вижу никаких очевидных проблем (но, возможно, я что-то упускаю). Было бы неплохо просмотреть ваш Java-код для сравнения, потому что там могут быть какие-то тонкие логические изменения, влияющие на производительность.
Комментарии:
1. Спасибо за советы. Сейчас моя производительность лучше, чем с вариантом Java. Я только что измерил приблизительно 1,9 с для вашего кода F #, работающего как сборка релиза в моем решении. Но у вас есть идея, почему последовательности в этом примере такие медленные? Мне, как новичку в F #, кажется, что эта последовательность должна иметь отличную производительность, и я подумал, что мне не нужно использовать императивный код, чтобы получить тот же результат. Может быть, у вас есть идея, почему an может мне сказать. Большое спасибо, кстати.
2.@FlyingHubert
Seq
— это интерфейс IEnumerable, который предоставляет итератор, но это сделает его немного медленнее, и вы будете торговать эффективностью памяти (потому что она ленива) с производительностью процессора. Seq / IEnumerable имеют неприятную привычку перечисляться несколько раз при цепочке. Если вы сомневаетесь, используйте массив для повышения производительности. Я не уверен, почемуseq
должна быть отличная производительность. Зависит от реализации. Императивный код, как правило, ближе к архитектуре ПК и быстрее, декларативный код абстрагируется больше, но за счет производительности.