code: def tile(cls, op: "DataFrameMerge"):
      left = build_concatenated_rows_frame(op.inputs[0])
      right = build_concatenated_rows_frame(op.inputs[1])
  
      ctx = get_context()
      auto_merge_threshold = op.auto_merge_threshold
      auto_merge_before, auto_merge_after = cls._get_auto_merge_options(
          op.auto_merge
      )
  
      if (
          auto_merge_before
          and len(left.chunks) + len(right.chunks)
          > auto_merge_threshold
      ):
          yield TileStatus(
              [left, right] + left.chunks + right.chunks, progress=0.2
          )
          left_chunk_size = len(left.chunks)
          right_chunk_size = len(right.chunks)
          left = auto_merge_chunks(ctx, left)
          right = auto_merge_chunks(ctx, right)
          logger.info(
              "Auto merge before %s, left data shape: %s, chunk count: %s -> %s, "
              "right data shape: %s, chunk count: %s -> %s.",
              op,
              left.shape,
              left_chunk_size,
              len(left.chunks),
              right.shape,
              right_chunk_size,
              len(right.chunks),
          )
      else:
          logger.info(
              "Skip auto merge before %s, left data shape: %s, chunk count: %d, "
              "right data shape: %s, chunk count: %d.",
              op,
              left.shape,
              len(left.chunks),
              right.shape,
              len(right.chunks),
          )
  
      method = cls._choose_merge_method(op, left, right)
      if cls._if_apply_bloom_filter(method, op, left, right):
          if has_unknown_shape(left, right):  # pragma: no cover
              yield TileStatus(left.chunks + right.chunks, progress=0.3)
          left_on = _prepare_shuffle_on(
              op.left_index, op.left_on, op.on
          )
          right_on = _prepare_shuffle_on(
              op.right_index, op.right_on, op.on
          )
          small_one = (
              right if len(left.chunks) > len(right.chunks) else left
          )
          logger.info(
              "Apply bloom filter for operand %s, use DataFrame %s to build bloom filter.",
              op,
              small_one,
          )
          left, right = yield from recursive_tile(
              *cls._apply_bloom_filter(
                  left, right, left_on, right_on, op
              )
          )
          # auto merge after bloom filter
          yield TileStatus(
              [left, right] + left.chunks + right.chunks, progress=0.5
          )
          left = auto_merge_chunks(ctx, left)
          right = auto_merge_chunks(ctx, right)
  
          if op.method == "auto":
              # if method is auto, select new method after auto merge
              method = cls._choose_merge_method(op, left, right)
      logger.info("Choose %s method for merge operand %s.", method, op)
      if method == MergeMethod.one_chunk:
          ret = cls._tile_one_chunk(op, left, right)
      elif method == MergeMethod.broadcast:
          ret = cls._tile_broadcast(op, left, right)
      else:
          assert method == MergeMethod.shuffle
          ret = cls._tile_shuffle(op, left, right)
  
      if (
          op.how == "inner"
          and auto_merge_after
          and len(ret[0].chunks) > auto_merge_threshold
      ):
          # if how=="inner", output data size will reduce greatly with high probability，
          # use auto_merge_chunks to combine small chunks.
          yield TileStatus(
              ret[0].chunks, progress=0.8
          )  # trigger execution for chunks
          merged = auto_merge_chunks(get_context(), ret[0])
          logger.info(
              "Auto merge after %s, data shape: %s, chunk count: %s -> %s.",
              op,
              merged.shape,
              len(ret[0].chunks),
              len(merged.chunks),
          )
          return [merged]
      else:
          logger.info(
              "Skip auto merge after %s, data shape: %s, chunk count: %d.",
              op,
              ret[0].shape,
              len(ret[0].chunks),
          )
          return ret
  
imports: import itertools
  from collections import namedtuple
  from enum import Enum
  from typing import Any, Dict, List, Optional, Union, Tuple
  import numpy
  import pandas
  from ...None import opcodes
  from ...core import OutputType, recursive_tile, TileStatus
  from ...core.context import get_context
  from ...core.operand import OperandStage, MapReduceOperand
  from ...serialization.serializables import AnyField, BoolField, DictField, StringField, TupleField, KeyField, Int32Field, NamedTupleField
  from ...typing import TileableType
  from ...utils import has_unknown_shape, lazy_import
  from ..base.bloom_filter import filter_by_bloom_filter
  from ..core import DataFrame, Series, DataFrameChunk
  from ..operands import DataFrameOperand, DataFrameOperandMixin, DataFrameShuffleProxy
  from ..utils import auto_merge_chunks, build_concatenated_rows_frame, build_df, parse_index, hash_dataframe_on, infer_index_value, is_cudf
  import logging
label_desc: Use image node_id 15 for calling the DataFrameMerge obj's underlying member method code with example usage: obj.tile(cls, op) and returns: list, ret