Python код замедляется при переносе в функцию - PullRequest
1 голос
/ 16 апреля 2020

Я читаю и обрабатываю файл (используя один и тот же бит кода), который работает с двумя очень разными скоростями: 1. Сценарий (50K + итераций в секунду) и 2. Обернут в функцию (~ 300 итераций в секунду). Я действительно не могу понять, почему существуют такие огромные различия в потреблении времени на чтение.

Структура модуля (неиспользуемые и нерелевантные файлы опущены. Код в конце.):

| experiments/
|--| experiment_runner.py
|
| module/
|--| shared/
|--|--| dataloaders.py
|--|--| data.py

В data.py у нас есть метод (load, обтекание класса метод наследуется от torch.utils.data.Dataset) фактической загрузки файла. В dataloaders.py я подготавливаю аргументы для передачи load, обернутые в функцию для каждого набора данных, который я использую. Затем он передается в функцию loader, которая обрабатывает разбиение набора данных и т. Д.

В experiment_runner, то есть, где происходят различия в скорости. Если я использую функции набора данных в dataloaders.py, загрузка происходит со скоростью около 300 итераций в секунду. Если я скопирую код из функции и брошу его непосредственно в experiment_runner, все еще используя функцию loader из dataloaders.py (то есть не обернутую в функцию для каждого набора данных), загрузка произойдет примерно с 50000 итераций / второй. Я в полной растерянности относительно того, почему обертывание кода в функцию может резко изменить его скорость.

Теперь актуальный код:

data.py:

    def load(self, dataset: str = 'train', skip_header = True, **kwargs) -> None:
        fp = open(self.data_files[dataset])

        if skip_header:
            next(fp)

        data = []
        for line in tqdm(self.reader(fp), desc = f'loading {self.name} ({dataset})'):
            data_line, datapoint = {}, base.Datapoint()

            for field in self.train_fields:
                idx = field.index if self.ftype in ['CSV', 'TSV'] else field.cname
                data_line[field.name] = self.process_doc(line[idx].rstrip())
                data_line['original'] = line[idx].rstrip()

            for field in self.label_fields:
                idx = field.index if self.ftype in ['CSV', 'TSV'] else field.cname
                if self.label_preprocessor:
                    data_line[field.name] = self.label_preprocessor(line[idx].rstrip())
                else:
                    data_line[field.name] = line[idx].rstrip()

            for key, val in data_line.items():
                setattr(datapoint, key, val)
            data.append(datapoint)
        fp.close()

        if self.length is None:
            # Get the max length
            lens = []
            for doc in data:
                for f in self.train_fields:
                    lens.append(len([tok for tok in getattr(doc, getattr(f, 'name'))]))
            self.length = max(lens)

        if dataset == 'train':
            self.data = data
        elif dataset == 'dev':
            self.dev = data
        elif dataset == 'test':
            self.test = data

dataloaders.py:

def loader(args: dict, **kwargs):
    """Loads the dataset.
    :args (dict): Dict containing arguments to load dataaset.
    :returns: Loaded and splitted dataset.
    """
    dataset = GeneralDataset(**args)
    dataset.load('train', **kwargs)

    if (args['dev'], args['test']) == (None, None):  # Only train set is given.
        dataset.split(dataset.data, [0.8, 0.1, 0.1], **kwargs)

    elif args['dev'] is not None and args['test'] is None:  # Dev set is given, test it not.
        dataset.load('dev', **kwargs)
        dataset.split(dataset.data, [0.8], **kwargs)

    elif args['dev'] is None and args['test'] is not None:  # Test is given, dev is not.
        dataset.split(dataset.data, [0.8], **kwargs)
        dataset.dev_set = dataset.test
        dataset.load('test', **kwargs)

    else:  # Both dev and test sets are given.
        dataset.load('dev', **kwargs)
        dataset.load('test', **kwargs)

    return dataset


def binarize(label: str) -> str:
    if label in ['0', '1']:
        return 'pos'
    else:
        return 'neg'


def datal(path: str, cleaners: base.Callable, preprocessor: base.Callable = None):
    args = {'data_dir': path,
            'ftype': 'csv',
            'fields': None,
            'train': 'dataset.csv', 'dev': None, 'test': None,
            'train_labels': None, 'dev_labels': None, 'test_labels': None,
            'sep': ',',
            'tokenizer': lambda x: x.split(),
            'preprocessor': preprocessor,
            'transformations': None,
            'length': None,
            'label_preprocessor': binarize,
            'name': 'First dataset.'
            }

    ignore = base.Field('ignore', train = False, label = False, ignore = True)
    d_text = base.Field('text', train = True, label = False, ignore = False, ix = 6, cname = 'text')
    d_label = base.Field('label', train = False, label = True, cname = 'label', ignore = False, ix = 5)

    args['fields'] = [ignore, ignore, ignore, ignore, ignore, d_label, d_text]

    return loader(args)

А для целей: эксперимент_runner. py

from module.dataloaders import datal, loader

dataset = datal() # Slow: 300-ish iterations/second

# Fast version: 50000 iter/second
def binarize(label: str) -> str:
    if label in ['0', '1']:
        return 'pos'
    else:
        return 'neg'

args = {'data_dir': path,
        'ftype': 'csv',
        'fields': None,
        'train': 'dataset.csv', 'dev': None, 'test': None,
        'train_labels': None, 'dev_labels': None, 'test_labels': None,
        'sep': ',',
        'tokenizer': lambda x: x.split(),
        'preprocessor': preprocessor,
        'transformations': None,
        'length': None,
        'label_preprocessor': binarize,
        'name': 'First dataset.'
        }

ignore = base.Field('ignore', train = False, label = False, ignore = True)
d_text = base.Field('text', train = True, label = False, ignore = False, ix = 6, cname = 'text')
d_label = base.Field('label', train = False, label = True, cname = 'label', ignore = False, ix = 5)

args['fields'] = [ignore, ignore, ignore, ignore, ignore, d_label, d_text]

dataset = loader(args)

В идеале я бы предпочел, чтобы функции набора данных (например, datal) были обернуты, чтобы разделить логи c, но с таким уменьшением скорости это неосуществимо.

...