https://gitlab.com/tezos/tezos
Raw File
Tip revision: 22c75d565d801c6b68247ceeca5408770f388e23 authored by Arvid Jakobsson on 19 October 2023, 09:05:38 UTC
Let the PEZification commence
Tip revision: 22c75d5
ticket_benchmarks.ml
(*****************************************************************************)
(*                                                                           *)
(* Open Source License                                                       *)
(* Copyright (c) 2022 Trili Tech, <contact@trili.com>                        *)
(* Copyright (c) 2023  Marigold <contact@marigold.dev>                       *)
(*                                                                           *)
(* Permission is hereby granted, free of charge, to any person obtaining a   *)
(* copy of this software and associated documentation files (the "Software"),*)
(* to deal in the Software without restriction, including without limitation *)
(* the rights to use, copy, modify, merge, publish, distribute, sublicense,  *)
(* and/or sell copies of the Software, and to permit persons to whom the     *)
(* Software is furnished to do so, subject to the following conditions:      *)
(*                                                                           *)
(* The above copyright notice and this permission notice shall be included   *)
(* in all copies or substantial portions of the Software.                    *)
(*                                                                           *)
(* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR*)
(* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,  *)
(* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL   *)
(* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER*)
(* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING   *)
(* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER       *)
(* DEALINGS IN THE SOFTWARE.                                                 *)
(*                                                                           *)
(*****************************************************************************)

open Protocol
open Benchmarks_proto
open Alpha_context

let ns = Namespace.make Registration.ns "tickets"

let fv s = Free_variable.of_namespace (ns s)

module Ticket_type_shared = struct
  type config = {max_size : int}

  let default_config = {max_size = Constants_repr.michelson_maximum_type_size}

  let config_encoding =
    let open Data_encoding in
    conv
      (fun {max_size} -> max_size)
      (fun max_size -> {max_size})
      (obj1 (req "max_size" int31))

  type workload = {nodes : int}

  let workload_encoding =
    let open Data_encoding in
    conv
      (function {nodes} -> nodes)
      (fun nodes -> {nodes})
      (obj1 (req "nodes" int31))

  let workload_to_vector {nodes} =
    Sparse_vec.String.of_list [("nodes", float_of_int nodes)]

  let tags = ["tickets"]
end

exception
  Ticket_benchmark_error of {
    benchmark_name : Namespace.t;
    trace : Tezos_base.TzPervasives.tztrace;
  }

(** A benchmark for {!Ticket_costs.Constants.cost_compare_ticket_hash}. *)
module Compare_ticket_hash_benchmark : Benchmark.S = struct
  type config = unit

  let config_encoding = Data_encoding.unit

  let default_config = ()

  type workload = unit

  let tags = ["tickets"]

  let workload_encoding = Data_encoding.unit

  let workload_to_vector () = Sparse_vec.String.of_list []

  let name = ns "COMPARE_TICKET_HASH"

  let info = "Compare cost for Ticket_hash"

  let module_filename = __FILE__

  let purpose = Benchmark.Generate_code "ticket"

  let group = Benchmark.Group "tickets"

  let model =
    Model.make
      ~conv:(fun () -> ())
      (Model.unknown_const1 ~const:(fv "compare_ticket_hash"))

  let create_benchmark ~rng_state _conf =
    let bytes = Base_samplers.bytes rng_state ~size:{min = 1; max = 64} in
    let hash =
      Ticket_hash.of_script_expr_hash @@ Script_expr_hash.hash_bytes [bytes]
    in
    let hash2 =
      Ticket_hash.of_script_expr_hash @@ Script_expr_hash.hash_bytes [bytes]
    in
    let workload = () in
    let closure () = ignore (Ticket_hash.compare hash hash2) in
    Generator.Plain {workload; closure}
end

let () = Registration.register (module Compare_ticket_hash_benchmark)

(** A benchmark for {!Ticket_costs.Constants.cost_compare_key_contract}.

    In this benchmark we only compare originated contracts; we never use
    implicit contracts. This is justified partly by the fact that
    currently the carbonated maps only use originated contracts as keys.
    In addition, while developing this benchmark the implicit contracts were
    also tested and gave almost identical timings. *)
module Compare_key_contract_benchmark : Benchmark.S = struct
  type config = unit

  let config_encoding = Data_encoding.unit

  let default_config = ()

  type workload = unit

  let workload_encoding = Data_encoding.unit

  let workload_to_vector () = Sparse_vec.String.of_list []

  let tags = ["tickets"]

  let name = ns "COMPARE_CONTRACT"

  let info = "Compare cost for Contracts"

  let module_filename = __FILE__

  let purpose = Benchmark.Generate_code "ticket"

  let group = Benchmark.Group "tickets"

  let model =
    Model.make
      ~conv:(fun () -> ())
      (Model.unknown_const1 ~const:(fv "compare_contract"))

  let create_benchmark ~rng_state _conf =
    let bytes = Base_samplers.bytes rng_state ~size:{min = 32; max = 64} in
    let branch = Block_hash.hash_bytes [bytes] in
    let op_hash = Operation.hash_raw {shell = {branch}; proto = bytes} in
    let nonce = Origination_nonce.Internal_for_tests.initial op_hash in
    let contract = Contract.Internal_for_tests.originated_contract nonce in
    let contract2 = Contract.Internal_for_tests.originated_contract nonce in
    let workload = () in
    let closure () = ignore (Contract.compare contract contract2) in
    Generator.Plain {workload; closure}
end

let () = Registration.register (module Compare_key_contract_benchmark)

(* A simple ticket type for use in the benchmarks. *)
let ticket_ty =
  let open Script_typed_ir in
  WithExceptions.Result.get_ok ~loc:__LOC__ (ticket_t (-1) int_t)

(* A dummy type generator, sampling linear terms of a given size.
   The generator always returns types of the shape:

   [pair int_or_ticket (pair int_or_ticket (pair int_or_ticket ...))]

   This is a worst case type for [type_has_tickets], though nested
   ors, nested maps or nested lists would be just as bad. *)
let rec dummy_type_generator ~rng_state size =
  let open Script_typed_ir in
  let ticket_or_int =
    if Base_samplers.uniform_bool rng_state then Ex_ty ticket_ty
    else Ex_ty int_t
  in
  if size <= 1 then ticket_or_int
  else
    match (ticket_or_int, dummy_type_generator ~rng_state (size - 3)) with
    | Ex_ty l, Ex_ty r -> (
        match pair_t (-1) l r with
        | Error _ -> assert false
        | Ok (Ty_ex_c t) -> Ex_ty t)

(** A benchmark for {!Ticket_costs.Constants.cost_has_tickets_of_ty}. *)
module Has_tickets_type_benchmark : Benchmark.S = struct
  include Ticket_type_shared

  let name = ns "TYPE_HAS_TICKETS"

  let info = "Benchmarking type_has_tickets"

  let module_filename = __FILE__

  let purpose = Benchmark.Generate_code "ticket"

  let group = Benchmark.Group "tickets"

  let make_bench_helper rng_state config () =
    let open Result_syntax in
    let* ctxt, _ = Lwt_main.run (Execution_context.make ~rng_state ()) in
    let ctxt = Gas_helpers.set_limit ctxt in
    let size = Random.State.int rng_state config.max_size in
    let (Ex_ty ty) = dummy_type_generator ~rng_state size in
    let nodes =
      let size = Script_typed_ir.ty_size ty in
      Saturation_repr.to_int @@ Script_typed_ir.Type_size.to_int size
    in
    let workload = {nodes} in
    let closure () = ignore (Ticket_scanner.type_has_tickets ctxt ty) in
    return (Generator.Plain {workload; closure})

  let create_benchmark ~rng_state config =
    match make_bench_helper rng_state config () with
    | Ok closure -> closure
    | Error trace ->
        raise (Ticket_benchmark_error {benchmark_name = name; trace})

  let model =
    Model.make
      ~takes_saturation_reprs:true
      ~conv:(function {nodes} -> (nodes, ()))
      Model.affine
end

let () = Registration.register (module Has_tickets_type_benchmark)

let ticket_sampler rng_state =
  let seed = Base_samplers.uniform_bytes ~nbytes:32 rng_state in
  let pkh, _, _ = Signature.generate_key ~algo:Signature.Ed25519 ~seed () in
  let ticketer = Alpha_context.Contract.Implicit pkh in
  Script_typed_ir.
    {ticketer; contents = Script_int.zero; amount = Ticket_amount.one}

(** A benchmark for {!Ticket_costs.Constants.cost_collect_tickets_step}. *)
module Collect_tickets_benchmark : Benchmark.S = struct
  include Ticket_type_shared

  let name = ns "COLLECT_TICKETS_STEP"

  let info = "Benchmarking tickets_of_value"

  let module_filename = __FILE__

  let purpose = Benchmark.Generate_code "ticket"

  let group = Benchmark.Group "tickets"

  let make_bench_helper rng_state config () =
    let open Script_typed_ir in
    let open Result_syntax in
    let* ctxt, _ = Lwt_main.run (Execution_context.make ~rng_state ()) in
    let ctxt = Gas_helpers.set_limit ctxt in
    let ty =
      match list_t (-1) ticket_ty with Error _ -> assert false | Ok t -> t
    in
    let _, elements =
      Structure_samplers.list
        ~range:{min = 0; max = config.max_size}
        ~sampler:ticket_sampler
        rng_state
    in
    let boxed_ticket_list = Script_list.of_list elements in
    Environment.wrap_tzresult
    @@ let* has_tickets, ctxt = Ticket_scanner.type_has_tickets ctxt ty in
       let workload = {nodes = Script_list.length boxed_ticket_list} in
       let closure () =
         ignore
           (Lwt_main.run
              (Ticket_scanner.tickets_of_value
                 ctxt
                 ~include_lazy:true
                 has_tickets
                 boxed_ticket_list))
       in
       return (Generator.Plain {workload; closure})

  let create_benchmark ~rng_state config =
    match make_bench_helper rng_state config () with
    | Ok closure -> closure
    | Error trace ->
        raise (Ticket_benchmark_error {benchmark_name = name; trace})

  let model = Model.make ~conv:(function {nodes} -> (nodes, ())) Model.affine
end

let () = Registration.register (module Collect_tickets_benchmark)
back to top