Fork me on GitHub

Benchee

Αυτή η μετάφραση είναι πλήρως ενημερωμένη.

Δεν μπορούμε απλά να υποθέτουμε ποιές συναρτήσεις είναι γρήγορες και ποιές αργές - χρειαζόμαστε πραγματικές μετρήσεις όταν είμαστε περίεργοι. Εδώ έχει θέση η συγκριτική αξιολόγηση. Σε αυτό το μάθημα, θα μάθουμε πόσο εύκολο είναι να μετρήσουμε την ταχύτητα του κώδικά μας.

Πίνακας περιεχομένων

Σχετικά με το Benchee

Παρόλο που υπάρχει μια συνάρτηση στην Erlang που μπορεί να χρησιμοποιηθεί για πολύ βασικές μετρήσεις του χρόνου εκτέλεσης μιας συνάρτησης, δεν είναι το ίδιο καλή στη χρήση σε σχέση με μερικά από τα διαθέσιμα εργαλεία και δεν σας δίνει πολλαπλές μετρήσεις για να πάρετε καλά στατιστικά. Για αυτό το λόγο θα χρησιμοποιήσουμε το Benchee. To Benchee μας παρέχει ένα εύρος στατιστικών με εύκολες συγκρίσεις μεταξύ σεναρίων, ένα τρομερό χαρακτηριστικό που μας επιτρέπει να ελέγξουμε διαφορετικές εισόδους στις συναρτήσεις που μετράμε και διάφορους μορφοποιητές για να εμφανίσουμε τα αποτελέσματά μας, καθώς και την ικανότητα να γράψουμε τον δικό μας μορφοποιητή αν το επιθυμούμε.

Χρήση

Για να προσθέσουμε το Benchee στο project μας, προσθέστε το σαν εξάρτηση στο mix.exs αρχείο σας:

defp deps do
  [{:benchee, "~> 0.9", only: :dev}]
end

Τότε καλούμε:

$ mix deps.get
...
$ mix compile

Η πρώτη εντολή θα κατεβάσει και εγκαταστήσει το Benchee. Μπορεί να σας ζητηθεί να εγκαταστήσετε το Hex μαζί με αυτό. Η δεύτερη συντάσσει την εφαρμογή Benchee. Τώρα είμαστε έτοιμοι να γράψουμε την πρώτη μας συγκριτική αξιολόγηση!

Μια σημαντική σημείωση πριν ξεκινήσουμε: Όταν αξιολογούμε, είναι πολύ σημαντικό να μην χρησιμοποιούμε το iex από τη στιγμή που συμπεριφέρεται διαφορετικά και είναι συχνά πιο αργό από τη συμπεριφορά του κώδικά σας στην παραγωγή. Έτσι, ας δημιουργήσουμε ένα αρχείο που θα ονομάσουμε benchmark.exs και σε αυτό το αρχείο ας βάλουμε τον παρακάτω κώδικα:

list = Enum.to_list(1..10_000)
map_fun = fn i -> [i, i * i] end

Benchee.run(%{
  "flat_map" => fn -> Enum.flat_map(list, map_fun) end,
  "map.flatten" => fn -> list |> Enum.map(map_fun) |> List.flatten() end
})

Τώρα για να τρέξουμε την συγκριτική αξιολόγηση, καλούμε:

$ mix run benchmark.exs

Και θα πρέπει να δούμε κάτι σαν την παρακάτω έξοδο στην κονσόλα σας:

Operating System: macOS
CPU Information: Intel(R) Core(TM) i5-4260U CPU @ 1.40GHz
Number of Available Cores: 4
Available memory: 8.589934592 GB
Elixir 1.5.1
Erlang 20.0
Benchmark suite executing with the following configuration:
warmup: 2.00 s
time: 5.00 s
parallel: 1
inputs: none specified
Estimated total run time: 14.00 s


Benchmarking flat_map...
Benchmarking map.flatten...

Name                  ips        average  deviation         median
flat_map           1.03 K        0.97 ms    ±33.00%        0.85 ms
map.flatten        0.56 K        1.80 ms    ±31.26%        1.60 ms

Comparison:
flat_map           1.03 K
map.flatten        0.56 K - 1.85x slower

Φυσικά οι πληροφορίες του συστήματός σας και τα αποτελέσματα μπορεί να διαφέρουν ανάλογα με τα χαρακτηριστικά του μηχανήματος που τρέχετε την συγκριτική αξιολόγηση, αλλά αυτές οι γενικές πληροφορίες θα πρέπει να είναι εκεί.

Με μια πρώτη ματιά, ο τομές Comparison μας δείχνει ότι η εκδοχή μας της map.flatten είναι 1.85 φορές πιο αργή από την flat_map - πολύ χρήσιμο να το γνωρίζουμε! Αλλά ας ρίξουμε μια ματιά και στα άλλα στατιστικά που παίρνουμε:

Υπάρχουν επίσης άλλα διαθέσιμα στατιστικά, αλλά αυτές οι τέσσερις είναι συχνά οι πιό χρήσιμες και συχνά χρησιμοποιούμενες για συγκριτικές αξιολογήσεις, γιαυτό και εμφανίζονται στον προκαθορισμένο μορφοποιητή. Για να μάθετε περισσότερα για τις άλλες διαθέσιμες μετρήσεις, ελέγξτε την τεκμηρίωση στα hexdocs.

Ρυθμίσεις

Ένα από καλύτερα μέρη του Benchee είναι οι διαθέσιμες επιλογές ρυθμίσεων. Θα δούμε πρώτα τα βασικά από τη στιγμή που δεν απαιτούν παραδείγματα κώδικα, και τότε θα δείξουμε πως να χρησιμοποιούμε ένα από τα καλύτερα χαρακτηριστικά του Benchee - τις εισαγωγές.

Βασικές

Το Benchee δέχεται μια πληθώρα επιλογών ρυθμίσεων. Στην πιο συχνή διεπαφή Benchee.run/2, αυτές περνάνε ως το δεύτερο όρισμα στη μορφή μιας προαιρετικές λίστας λέξεων κλειδί:

Benchee.run(
  %{"example function" => fn -> "hi!" end},
  warmup: 4,
  time: 10,
  inputs: nil,
  parallel: 1,
  formatters: [&Benchee.Formatters.Console.output/1],
  print: [
    benchmarking: true,
    configuration: true,
    fast_warning: true
  ],
  console: [
    comparison: true,
    unit_scaling: :best
  ]
)

Οι διαθέσιμες επιλογές είναι οι ακόλουθες (επίσης τεκμηριωμένες στα hexdocs).

Είσοδοι

Είναι πολύ σημαντικό να κάνουμε συγκριτικές αξιολογήσεις στις συναρτήσεις μας με δεδομένα που αντικατοπρίζουν τα πραγματικά δεδομένα στα οποία θα δράσει η συνάρτηση στην πραγματικότητα. Συχνά η συνάρτηση μπορεί να συμπεριφέρεται διαφορετικά με μικρά σετ δεδομένων από ότι σε μεγάλα σετ δεδομένων! Εδώ δρα η επιλογή ρύθμισης inputs του Benchee. Σας επιτρέπει να ελέγξετε την ίδια συνάρτηση, αλλά με όσες διαφορετικές εισόδους θέλετε, και να δείτε τα αποτελέσματα της συγκριτικής αξιολόγησης για κάθε μία από αυτές τις συναρτήσεις.

Έτσι, ας δούμε το αρχικό μας παράδειγμα ξανά:

list = Enum.to_list(1..10_000)
map_fun = fn i -> [i, i * i] end

Benchee.run(%{
  "flat_map" => fn -> Enum.flat_map(list, map_fun) end,
  "map.flatten" => fn -> list |> Enum.map(map_fun) |> List.flatten() end
})

Σε αυτό το παράδειγμα χρησιμοποιούμε μόνο μια μονή λίστα ακέραιων από το 1 έως το 10,000. Ας το αλλάξουμε ώστε να χρησιμοποιούμε μερικές διαφορετικές εισόδους ώστε να δούμε τι συμβαίνει με τις μικρότερες και μεγαλύτερες λίστες. Έτσι, ανοίξτε το αρχείο, και αλλάξτε το ώστε να μοιάζει ως εξής:

map_fun = fn i -> [i, i * i] end

inputs = %{
  "small list" => Enum.to_list(1..100),
  "medium list" => Enum.to_list(1..10_000),
  "large list" => Enum.to_list(1..1_000_000)
}

Benchee.run(
  %{
    "flat_map" => fn list -> Enum.flat_map(list, map_fun) end,
    "map.flatten" => fn list -> list |> Enum.map(map_fun) |> List.flatten() end
  },
  inputs: inputs
)

Θα παρατηρήσετε δύο διαφορές. Πρώτον, τώρα έχουμε ένα χάρτη inputs που περιέχει την πληροφορία για τις εισαγωγές των συναρτήσεών μας. Αυτό τον χάρτη εισόδου τον περνάμε σαν επιλογή ρυθμίσεων στην Benchee.run/2.

Και από τη στιγμή που οι συναρτήσεις μας χρειάζονται να δεχτούν ένα όρισμα τώρα, πρέπει να αλλάξουμε τις συναρτήσεις συγκριτικών αξιολογήσεων να δέχονται ένα όρισμα, έτσι αντί για:

fn -> Enum.flat_map(list, map_fun) end

τώρα έχουμε:

fn(list) -> Enum.flat_map(list, map_fun) end

Ας το ξανατρέξουμε χρησιμοποιώντας:

$ mix run benchmark.exs

Τώρα θα πρέπει να δείτε έξοδο στην κονσόλα σας ως εξής:

Operating System: macOS
CPU Information: Intel(R) Core(TM) i5-4260U CPU @ 1.40GHz
Number of Available Cores: 4
Available memory: 8.589934592 GB
Elixir 1.5.1
Erlang 20.0
Benchmark suite executing with the following configuration:
warmup: 2.00 s
time: 5.00 s
parallel: 1
inputs: large list, medium list, small list
Estimated total run time: 2.10 min

Benchmarking with input large list:
Benchmarking flat_map...
Benchmarking map.flatten...

Benchmarking with input medium list:
Benchmarking flat_map...
Benchmarking map.flatten...

Benchmarking with input small list:
Benchmarking flat_map...
Benchmarking map.flatten...


##### With input large list #####
Name                  ips        average  deviation         median
flat_map             6.29      158.93 ms    ±19.87%      160.19 ms
map.flatten          4.80      208.20 ms    ±23.89%      200.11 ms

Comparison:
flat_map             6.29
map.flatten          4.80 - 1.31x slower

##### With input medium list #####
Name                  ips        average  deviation         median
flat_map           1.34 K        0.75 ms    ±28.14%        0.65 ms
map.flatten        0.87 K        1.15 ms    ±57.91%        1.04 ms

Comparison:
flat_map           1.34 K
map.flatten        0.87 K - 1.55x slower

##### With input small list #####
Name                  ips        average  deviation         median
flat_map         122.71 K        8.15 μs   ±378.78%        7.00 μs
map.flatten       86.39 K       11.58 μs   ±680.56%       10.00 μs

Comparison:
flat_map         122.71 K
map.flatten       86.39 K - 1.42x slower

Μπορούμε τώρα να δούμε πληροφορίες για τις συγκριτικές αξιολογήσεις μας, συγκεντρωμένες κατά είσοδο. Αυτό το απλό παράδειγμα δεν μας παρέχει καμμιά εξαιρετική επίγνωση, αλλά θα εκπλαγείτε στο κατά πόσο διαφέρουν οι επιδόσεις ανάλογα με το μέγεθος εισαγωγής!

Μορφοποιητές

Η έξοδος κονσόλας που είδαμε είναι πολύ χρήσιμη σαν αρχή για τις μετρήσεις των εκτελέσεων των συναρτήσεών σας, αλλά δεν είναι η μόνη σας επιλογή! Σε αυτό τον τομέα θα δούμε σύντομα τους τρεις άλλους διαθέσιμους μορφοποιητές, και επίσης θα κάνουμε μια αρχή στο πως μπορείτε να γράψετε τον δικό σας μορφοποιητή αν θέλετε.

Άλλοι μορφοποιητές

Το Benchee έχει ένα μορφοποιητή κονσόλας προκαθορισμένο, ο οποίος είναι αυτός που έχουμε δει ήδη, αλλά υπάρχουν άλλοι τρεις επίσημα υποστηριζόμενοι - οι benchee_csv, benchee_json και benchee_html. Κάθε ένας από αυτούς κάνει ακριβώς αυτό που θα περιμένατε, το οποίο είναι να γράφει τα αποτελέσματα στο αντίστοιχο τύπο αρχείου ώστε να μπορείτε να δουλέψετε με τα αποτελέσματα περαιτέρω σε οποιοδήποτε φορμάτ θέλετε.

Κάθε ένας από αυτούς τους μορφοποιητές είναι ένα ξεχωριστό πακέτο, έτσι για να τους χρησιμοποιήσετε πρέπει να τους προσθέσετε σαν εξαρτήσεις στο αρχείο mix.exs σας ως εξής:

defp deps do
  [
    {:benchee_csv, "~> 0.6", only: :dev},
    {:benchee_json, "~> 0.3", only: :dev},
    {:benchee_html, "~> 0.3", only: :dev}
  ]
end

Παρόλο που οι benchee_json και benchee_csv είναι πολύ απλές, η benchee_html είναι στην πραγματικότητα πολύ πλήρης σε χαρακτηριστικά! Μπορεί να σας βοηθήσει να δημιουργήσετε όμορφα γραφήματα και διαγράμματα από τα αποτελέματά σας πολύ εύκολα και ακόμα και να τα εξάγετε σαν εικόνες PNG. Όλοι οι τρεις μορφοποιητές είναι πολύ καλά τεκμηριωμένοι στις αντίστοιχες σελίδες τους στο GitHub, έτσι δεν θα καλύψουμε τις λεπτομέρειες τους εδώ.

Ειδικοί Μορφοποιητές

Αν οι τέσσερις προσφερόμενοι μορφοποιητές δεν είναι αρκετοί για εσάς, μπορείτε επίσης να γράψετε το δικό σας μορφοποιητή, το οποίο είναι σχετικά εύκολο. Πρέπει να γράψετε μια συνάρτηση που δέχεται μια δομή %Benchee.Suite{}, και από αυτή μπορείτε να τραβήξετε ότι πληροφορία θέλετε. Πληροφορίες για το τι περιλαμβάνεται σε αυτή τη δομή μπορούν να βρεθούν στο GitHub ή στα HexDocs. Ο κώδικας είναι πολύ καλά τεκμηριωμένος και εύκολα αναγνώσιμος αν θα θέλατε να δείτε του τι είδους πληροφορίες μπορούν να είναι διαθέσιμες για την εγγραφή των δικών σας μορφοποιητών.

Για τώρα, θα δείξω ένα γρήγορο παράδειγμα του πως μπορεί να δείχνει ένας παραμετροποιημένος μορφοποιητής σαν παράδειγμα του πόσο εύκολο είναι. Ας πούμε ότι θέλουμε απλά έναν απλό μορφοποιητή που τυπώνει μόνο τον μέσο χρόνο εκτέλεσης για κάθε σενάριο - θα δείχνει κάπως έτσι:

defmodule Custom.Formatter do
  def output(suite) do
    suite
    |> format
    |> IO.write()

    suite
  end

  defp format(suite) do
    Enum.map_join(suite.scenarios, "\n", fn scenario ->
      "Average for #{scenario.job_name}: #{scenario.run_time_statistics.average}"
    end)
  end
end

Και τότε θα μπορούμενα τρέξουμε τη συγκριτική αξιολόγησή μας ως εξής:

list = Enum.to_list(1..10_000)
map_fun = fn i -> [i, i * i] end

Benchee.run(
  %{
    "flat_map" => fn -> Enum.flat_map(list, map_fun) end,
    "map.flatten" => fn -> list |> Enum.map(map_fun) |> List.flatten() end
  },
  formatters: [&Custom.Formatter.output/1]
)

Και όταν τρέξουμε τώρα με τον τρέχων μορφοποιητή, θα πρέπει να δούμε:

Operating System: macOS
CPU Information: Intel(R) Core(TM) i5-4260U CPU @ 1.40GHz
Number of Available Cores: 4
Available memory: 8.589934592 GB
Elixir 1.5.1
Erlang 20.0
Benchmark suite executing with the following configuration:
warmup: 2.00 s
time: 5.00 s
parallel: 1
inputs: none specified
Estimated total run time: 14.00 s


Benchmarking flat_map...
Benchmarking map.flatten...
Average for flat_map: 851.8840109326956
Average for map.flatten: 1659.3854339873628

Contributors

loading...



Share This Page