def _run_scorer_parallelizable(
  scorer: Scorer,
  runParallel: bool,
  scoringArgs: ScoringArgs,
  dataLoader: Optional[CommunityNotesDataLoader] = None,
  scoringArgsSharedMemory=None,
) -> Tuple[ModelResult, float]:
  """
  Run scoring (either prescoring or final scoring) for a single scorer.
  This function is designed to be run in a multiprocessing pool, so you can run this function
  for each scorer in parallel.

  We determine whether to run prescoring or final scoring based on the type of scoringArgs
    (PrescoringArgs or FinalScoringArgs).

  If runParallel is False, then we read input dataframes from scoringArgs.

  If runParallel is True, then we ignore the dataframe attributes of scoringArgs, and read
  the input dataframes from shared memory if scoringArgsSharedMemory is not None (preferred),
  or from the dataLoader if scoringArgsSharedMemory is None. However, using the dataLoader to
  re-read the dataframes from disk is much slower than using shared memory and is deprecated.
  """
  scorerStartTime = time.perf_counter()

  # Load data if multiprocessing
  if runParallel:
    with c.time_block(f"{scorer.get_name()} run_scorer_parallelizable: Loading data"):
      scoringArgs.remove_large_args_for_multiprocessing()  # Should be redundant
      scoringArgs = copy.deepcopy(scoringArgs)

      if scoringArgsSharedMemory is not None:
        logger.info(
          f"{scorer.get_name()} run_scorer_parallelizable just started in parallel: loading data from shared memory."
        )
        scoringArgs = _load_data_from_shared_memory_parallelizable(
          scoringArgsSharedMemory, scoringArgs
        )
        logger.info(
          f"{scorer.get_name()} run_scorer_parallelizable just finished loading data from shared memory."
        )
      elif dataLoader is not None:
        logger.info(
          f"{scorer.get_name()} run_scorer_parallelizable just started in parallel: loading data with dataLoader."
        )
        scoringArgs = _load_data_with_data_loader_parallelizable(dataLoader, scoringArgs)
      else:
        raise ValueError(
          "Must provide either scoringArgsSharedMemory or dataLoader to run parallel"
        )

  # Run scoring
  scorerStartTime = time.perf_counter()
  if type(scoringArgs) == PrescoringArgs:
    scoringResults = scorer.prescore(scoringArgs, preserveRatings=not runParallel)
  elif type(scoringArgs) == FinalScoringArgs:
    scoringResults = scorer.score_final(scoringArgs)
  else:
    raise ValueError(f"Unknown scoringArgs type: {type(scoringArgs)}")
  scorerEndTime = time.perf_counter()

  return scoringResults, (scorerEndTime - scorerStartTime)

  Run scoring (either prescoring or final scoring) for a single scorer.
  This function is designed to be run in a multiprocessing pool, so you can run this function
  for each scorer in parallel.

  We determine whether to run prescoring or final scoring based on the type of scoringArgs
    (PrescoringArgs or FinalScoringArgs).

  If runParallel is False, then we read input dataframes from scoringArgs.

  If runParallel is True, then we ignore the dataframe attributes of scoringArgs, and read
  the input dataframes from shared memory if scoringArgsSharedMemory is not None (preferred),
  or from the dataLoader if scoringArgsSharedMemory is None. However, using the dataLoader to
  re-read the dataframes from disk is much slower than using shared memory and is deprecated.
  
sourcecode/scoring/run_scoring.py