





"""simple docstring"""




from __future__ import annotations

from collections.abc import Callable
from typing import Generic, TypeVar

a		:					Any	      =     TypeVar('''T''')
a		:					List[Any]	      =     TypeVar('''U''')


class 			__UpperCamelCase							(		Generic[T, U]		):






							def __init__(  self ,   lowerCAmelCase__ ,   lowerCAmelCase__ )      ->					int:
								a :						Optional[Any]      =							key
								a :						Dict      =							val
								a :						DoubleLinkedListNode[T, U] | None      =							None
								a :						DoubleLinkedListNode[T, U] | None      =							None






							def __repr__(  self )      ->					str:
								return (
								    f"""Node: key: {self.key}, val: {self.val}, """
								    f"""has next: {bool(self.next )}, has prev: {bool(self.prev )}"""
								)


class 			__UpperCamelCase							(		Generic[T, U]		):






							def __init__(  self )      ->					None:
								a :						DoubleLinkedListNode[T, U]      =							DoubleLinkedListNode(lowerCAmelCase__ ,   lowerCAmelCase__ )
								a :						DoubleLinkedListNode[T, U]      =							DoubleLinkedListNode(lowerCAmelCase__ ,   lowerCAmelCase__ )
								a,			a :						Union[str, Any]      =							self.rear, self.head






							def __repr__(  self )      ->					str:
								a :						Any      =							["DoubleLinkedList"]
								a :						Dict      =							self.head
								while node.next is not None:
									rep.append(str(lowerCAmelCase__ ) )
									a :						Union[str, Any]      =							node.next
								rep.append(str(self.rear ) )
								return ",\n    ".join(lowerCAmelCase__ )






							def        __a  (  self ,   lowerCAmelCase__ )      ->					None:
								a :						Optional[int]      =							self.rear.prev

								# All nodes other than self.head are guaranteed to have non-None previous
								assert previous is not None

								a :						str      =							node
								a :						List[Any]      =							previous
								a :						Optional[int]      =							node
								a :						Optional[Any]      =							self.rear






							def        __a  (  self ,   lowerCAmelCase__ )      ->					DoubleLinkedListNode[T, U] | None:

								if node.prev is None or node.next is None:
									return None

								a :						Optional[Any]      =							node.next
								a :						Union[str, Any]      =							node.prev
								a :						List[Any]      =							None
								a :						int      =							None
								return node


class 			__UpperCamelCase							(		Generic[T, U]		):
							lowerCamelCase  :   dict[Callable[[T], U], LRUCache[T, U]]    ={}






							def __init__(  self ,   lowerCAmelCase__ )      ->					int:
								a :						DoubleLinkedList[T, U]      =							DoubleLinkedList()
								a :						Union[str, Any]      =							capacity
								a :						Optional[Any]      =							0
								a :						List[str]      =							0
								a :						int      =							0
								a :						dict[T, DoubleLinkedListNode[T, U]]      =							{}






							def __repr__(  self )      ->					str:
								return (
								    f"""CacheInfo(hits={self.hits}, misses={self.miss}, """
								    f"""capacity={self.capacity}, current size={self.num_keys})"""
								)






							def __contains__(  self ,   lowerCAmelCase__ )      ->					bool:
								return key in self.cache






							def        __a  (  self ,   lowerCAmelCase__ )      ->					U | None:
								# Note: pythonic interface would throw KeyError rather than return None

								if key in self.cache:
									self.hits += 1
									a :						DoubleLinkedListNode[T, U]      =							self.cache[key]
									a :						Tuple      =							self.list.remove(self.cache[key] )
									assert node == value_node

									# node is guaranteed not None because it is in self.cache
									assert node is not None
									self.list.add(lowerCAmelCase__ )
									return node.val
								self.miss += 1
								return None






							def        __a  (  self ,   lowerCAmelCase__ ,   lowerCAmelCase__ )      ->					None:

								if key not in self.cache:
									if self.num_keys >= self.capacity:
										# delete first node (oldest) when over capacity
										a :						List[str]      =							self.list.head.next

										# guaranteed to have a non-None first node when num_keys > 0
										# explain to type checker via assertions
										assert first_node is not None
										assert first_node.key is not None
										assert (
										    self.list.remove(lowerCAmelCase__ ) is not None
										)  # node guaranteed to be in list assert node.key is not None

										del self.cache[first_node.key]
										self.num_keys -= 1
									a :						Optional[int]      =							DoubleLinkedListNode(lowerCAmelCase__ ,   lowerCAmelCase__ )
									self.list.add(self.cache[key] )
									self.num_keys += 1

								else:
									# bump node to the end of the list, update value
									a :						Tuple      =							self.list.remove(self.cache[key] )
									assert node is not None  # node guaranteed to be in list
									a :						List[str]      =							value
									self.list.add(lowerCAmelCase__ )






							@classmethod
							def        __a  (  cls ,   lowerCAmelCase__ = 128 )      ->					Callable[[Callable[[T], U]], Callable[..., U]]:

								def cache_decorator_inner(lowerCAmelCase__ ) -> Callable[..., U]:
									def cache_decorator_wrapper(*lowerCAmelCase__ ) -> U:
										if func not in cls.decorator_function_to_instance_map:
											a :						Optional[Any]      =							LRUCache(lowerCAmelCase__ )

										a :						Any      =							cls.decorator_function_to_instance_map[func].get(args[0] )
										if result is None:
											a :						Optional[int]      =							func(*lowerCAmelCase__ )
											cls.decorator_function_to_instance_map[func].put(args[0] ,   lowerCAmelCase__ )
										return result

									def cache_info() -> LRUCache[T, U]:
										return cls.decorator_function_to_instance_map[func]

									setattr(lowerCAmelCase__ ,   "cache_info" ,   lowerCAmelCase__ )  # noqa: B010

									return cache_decorator_wrapper

								return cache_decorator_inner


if __name__ == "__main__":
		import doctest

		doctest.testmod()









"""simple docstring"""




import gc
import threading
import time

import psutil
import torch


class 			__UpperCamelCase							:






							def __init__(  self )      ->					Any:
								a :						str      =							psutil.Process()
								a :						str      =							False






							def        __a  (  self )      ->					str:
								a :						List[Any]      =							-1

								while True:
									a :						str      =							max(self.process.memory_info().rss ,   self.cpu_memory_peak )

									# can't sleep or will not catch the peak right (this comment is here on purpose)
									if not self.peak_monitoring:
										break






							def        __a  (  self )      ->					List[str]:
								a :						Dict      =							True
								a :						Optional[Any]      =							threading.Thread(target=self.peak_monitor )
								a :						List[Any]      =							True
								self.thread.start()






							def        __a  (  self )      ->					List[str]:
								a :						Optional[int]      =							False
								self.thread.join()
								return self.cpu_memory_peak
a		:					Tuple	      =     PeakCPUMemory()






def  _SCREAMING_SNAKE_CASE						(      )					->List[Any]:






	'''simple docstring'''






	a :						List[str]      =							{"time": time.time()}

	gc.collect()
	torch.cuda.empty_cache()

	# CPU mem
	a :						Tuple      =							psutil.Process().memory_info().rss
	cpu_peak_tracker.start()

	# GPU mem
	for i in range(torch.cuda.device_count()   ):
		a :						Any      =							torch.cuda.memory_allocated(_lowercase   )
	torch.cuda.reset_peak_memory_stats()

	return measures






def  _SCREAMING_SNAKE_CASE						(      _lowercase		:       List[str]   )					->Union[str, Any]:






	'''simple docstring'''






	a :						int      =							{"time": time.time() - start_measures["time"]}

	gc.collect()
	torch.cuda.empty_cache()

	# CPU mem
	a :						List[Any]      =							(psutil.Process().memory_info().rss - start_measures["cpu"]) / 2**20
	a :						Union[str, Any]      =							(cpu_peak_tracker.stop() - start_measures["cpu"]) / 2**20

	# GPU mem
	for i in range(torch.cuda.device_count()   ):
		a :						Optional[Any]      =							(torch.cuda.memory_allocated(_lowercase   ) - start_measures[str(_lowercase   )]) / 2**20
		a :						Optional[Any]      =							(torch.cuda.max_memory_allocated(_lowercase   ) - start_measures[str(_lowercase   )]) / 2**20

	return measures







def  _SCREAMING_SNAKE_CASE						(      _lowercase		:       str       ,			_lowercase		:       Union[str, Any]   )					->Optional[int]:






	'''simple docstring'''






	print(F"""{description}:"""   )
	print(F"""- Time: {measures['time']:.2f}s"""   )
	for i in range(torch.cuda.device_count()   ):
		print(F"""- GPU {i} allocated: {measures[str(_lowercase   )]:.2f}MiB"""   )
		a :						str      =							measures[F"""{i}-peak"""]
		print(F"""- GPU {i} peak: {peak:.2f}MiB"""   )
	print(F"""- CPU RAM allocated: {measures['cpu']:.2f}MiB"""   )
	print(F"""- CPU RAM peak: {measures['cpu-peak']:.2f}MiB"""   )


