A Merge Sort implementation for efficiency This is my version of mergesort.
# mergesort

def merge(items, low, mid, high):
    left = [items[i] for i in range(low, mid + 1)]
    right = [items[i] for i in range(mid + 1, high + 1)]
    left.append(4444444444444444444444)
    right.append(4444444444444444444444)

    i = 0
    j = 0
    for k in range(low, high + 1):
        if left[i] &lt; right[j]:
            items[k] = left[i]
            i += 1
        else:
            items[k] = right[j]
            j += 1

def mergesort(items,low, high):
    mid = int(low + (high - low) / 2)
    if low &lt; high:
        mergesort(items, low, mid)
        mergesort(items, mid + 1, high)
        merge(items, low, mid, high)


if __name__ == &quot;__main__&quot;:
    a = [6, 5, 4, 3, 2, 1]
    print(mergesort(a, 0, len(a) - 1))

I ran a test with input size of 1000000 in reverse order with a step of 1 i.e range(int(1e6), 1, -1) or range(1000000, 1, -1)  and got the following results
6999991 function calls (4999995 primitive calls) in 9.925 seconds

   Ordered by: internal time
   List reduced from 9 to 8 due to restriction &lt;8&gt;

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
   999998    6.446    0.000    8.190    0.000 test.py:3(merge)
1999997/1    1.735    0.000    9.925    9.925 test.py:19(mergesort)
   999998    0.821    0.000    0.821    0.000 test.py:4(&lt;listcomp&gt;)
   999998    0.740    0.000    0.740    0.000 test.py:5(&lt;listcomp&gt;)
  1999996    0.183    0.000    0.183    0.000 {method 'append' of 'list' objects}
        1    0.000    0.000    9.925    9.925 {built-in method builtins.exec}
        1    0.000    0.000    9.925    9.925 &lt;string&gt;:1(&lt;module&gt;)
        1    0.000    0.000    0.000    0.000 {built-in method builtins.len}


I would love to optimize the code and if possible, eliminate the copies.
Some comments:

Include a convenience function that takes only a list, i.e., where you don't need to provide the bounds.
Don't fight Python's inclusive-start exclusive-stop paradigm and naming.
Write docstrings to explain what the parameters mean, especially if you use unusual stuff.
Don't compute mid when you don't have to anyway.
Compute mid with integer division and from the sum (Python's ints don't overflow).
Don't reimplement slicing.
You can easily do it with copying out just the left part.
For benchmarking, I'd suggest to randomly shuffle the list instead of using a reverse-sorted list. I suspect you did the latter because you thought it's a worst case, but for example for Python's built-in sorting algorithm Timsort, it's a &quot;best case&quot;, taking only linear time. And other algorithms might also be unrealistically affected (either faster or slower than for the typical case).
Don't write an unstable merge sort, that's a sin :-P
4444444444444444444444 is unsafe. What if the list has items larger than that?

Here's my version of it, it's about three times as fast as yours both on reverse-sorted and on shuffled lists (tested with 100,000 items).
def merge(items, start, mid, stop):
    &quot;&quot;&quot;Merge sorted items[start:mid] and items[mid:stop] into items[start:stop].&quot;&quot;&quot;
    for left in items[start:mid]:
        while mid &lt; stop and items[mid] &lt; left:
            items[start] = items[mid]
            start += 1
            mid += 1
        items[start] = left
        start += 1

def mergesort_slice(items, start, stop):
    &quot;&quot;&quot;Sort items[start:stop].&quot;&quot;&quot;
    if stop - start &gt; 1:
        mid = (start + stop) // 2
        mergesort_slice(items, start, mid)
        mergesort_slice(items, mid, stop)
        merge(items, start, mid, stop)

def mergesort(items):
    mergesort_slice(items, 0, len(items))