Skip to content

Commit 4921327

Browse files
tomvdwSeqIO
authored andcommitted
Make sure tf.io.gfile.glob is only executed once in SeqIO's list_shards
Multiple threads could trigger executing the same glob multiple times, which is slow. PiperOrigin-RevId: 739922342
1 parent bb079d8 commit 4921327

4 files changed

Lines changed: 59 additions & 27 deletions

File tree

seqio/dataset_providers.py

Lines changed: 1 addition & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
import collections
2424
import dataclasses
2525
import functools
26-
import glob
2726
import inspect
2827
import json
2928
import numbers
@@ -61,8 +60,6 @@
6160
Feature = utils.Feature
6261

6362

64-
65-
6663
@dataclasses.dataclass(frozen=True)
6764
class ContinuousFeature(Feature):
6865
"""A container for multi-modal output features of data providers."""
@@ -592,12 +589,6 @@ def _get_filename(info):
592589

593590

594591

595-
def _list_files(pattern: str) -> Sequence[str]:
596-
# Ensure that all machines observe the list of files in the same order and
597-
# unique.
598-
return sorted(set(tf.io.gfile.glob(pattern)))
599-
600-
601592
class FileDataSource(DataSource):
602593
"""A `DataSource` that reads a file to provide the input dataset."""
603594

@@ -714,22 +705,9 @@ def get_dataset(
714705
num_parallel_calls=tf.data.experimental.AUTOTUNE,
715706
)
716707

717-
@functools.lru_cache(maxsize=1024)
718708
def list_shards(self, split: str) -> Sequence[str]:
719709
filepattern = self._split_to_filepattern[split]
720-
if isinstance(filepattern, str):
721-
return _list_files(pattern=filepattern)
722-
723-
filepattern = list(filepattern)
724-
725-
if not any(glob.has_magic(f) for f in filepattern):
726-
return filepattern
727-
else:
728-
assert isinstance(filepattern, Iterable)
729-
ret = []
730-
for f in filepattern:
731-
ret.extend(_list_files(pattern=f))
732-
return ret
710+
return utils.list_files(filepattern)
733711

734712

735713

seqio/dataset_providers_test.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2121,12 +2121,12 @@ def test_repr(self):
21212121
" block_length=16)",
21222122
)
21232123

2124-
@mock.patch.object(dataset_providers, "_list_files")
2124+
@mock.patch.object(utils, "_list_files_for_glob")
21252125
def test_file_data_source_shuffle_buffer_low(self, mock_list_files):
21262126
mock_list_files.return_value = [f"{i}" for i in range(20)]
21272127
fds = dataset_providers.FileDataSource(
21282128
read_file_fn=lambda x: tf.data.Dataset.from_tensor_slices([x]),
2129-
split_to_filepattern={"train": "filepattern"},
2129+
split_to_filepattern={"train": "filepattern*"},
21302130
file_shuffle_buffer_size=2,
21312131
)
21322132
for _ in range(10):
@@ -2162,12 +2162,12 @@ def test_file_data_source_shuffle_buffer_low(self, mock_list_files):
21622162
],
21632163
)
21642164

2165-
@mock.patch.object(dataset_providers, "_list_files")
2165+
@mock.patch.object(utils, "_list_files_for_glob")
21662166
def test_file_data_source_shuffle_buffer_full(self, mock_list_files):
21672167
mock_list_files.return_value = [f"{i}" for i in range(20)]
21682168
fds = dataset_providers.FileDataSource(
21692169
read_file_fn=lambda x: tf.data.Dataset.from_tensor_slices([x]),
2170-
split_to_filepattern={"train": "filepattern"},
2170+
split_to_filepattern={"train": "filepattern*"},
21712171
file_shuffle_buffer_size=None,
21722172
)
21732173
for _ in range(10):

seqio/utils.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,11 @@
1818
import contextlib
1919
import dataclasses
2020
import functools
21+
import glob
2122
import inspect
2223
import os
2324
import re
25+
import threading
2426
import types
2527
from typing import Any, Callable, Dict, Iterable, Mapping, Optional, Sequence, Tuple, Type, Union
2628

@@ -1597,3 +1599,45 @@ def function_name(function) -> str:
15971599
return ""
15981600

15991601

1602+
# Lock to prevent multiple threads from calling tf.io.gfile.glob on the same
1603+
# file pattern at the same time.
1604+
_LIST_SHARD_LOCKS: dict[str, threading.Lock] = collections.defaultdict(
1605+
threading.Lock
1606+
)
1607+
1608+
1609+
def list_files(file_patterns: Union[str, Iterable[str]]) -> list[str]:
1610+
"""Returns a sorted list of file for the given file pattern.
1611+
1612+
Note that shard patterns like `foo@2` are not expanded. Only glob patterns
1613+
are expanded, e.g., `foo*`.
1614+
1615+
Args:
1616+
file_patterns: A string or an iterable of strings, each of which is a file
1617+
pattern to expand.
1618+
"""
1619+
if isinstance(file_patterns, str):
1620+
file_patterns = [file_patterns]
1621+
1622+
result = []
1623+
for file_pattern in file_patterns:
1624+
if not glob.has_magic(file_pattern):
1625+
result.append(file_pattern)
1626+
continue
1627+
# Make sure only one thread is calling tf.io.gfile.glob on the same file
1628+
# pattern at the same time. The other threads will wait for the lock to be
1629+
# released, and then use the cached result.
1630+
with _LIST_SHARD_LOCKS[file_pattern]:
1631+
result.extend(_list_files_for_glob(file_pattern))
1632+
return result
1633+
1634+
1635+
1636+
1637+
@functools.lru_cache(maxsize=1024)
1638+
def _list_files_for_glob(file_pattern: str) -> list[str]:
1639+
"""Returns a sorted list of files for the given glob file pattern."""
1640+
file_names = set(tf.io.gfile.glob(file_pattern))
1641+
return sorted(file_names)
1642+
1643+

seqio/utils_test.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1160,6 +1160,16 @@ def test_shift_right_by_one_without_default_bos(self):
11601160
self.assertAllEqual(actual, expected)
11611161
self.assertEqual(actual.dtype, np.int32)
11621162

1163+
@mock.patch.object(tf.io.gfile, "glob", autospec=True)
1164+
def test_list_files(self, mock_tf_glob):
1165+
mock_tf_glob.return_value = ["/bar1", "/bar2"]
1166+
result = utils.list_files(["/foo@2", "/bar*"])
1167+
self.assertEqual(result, ["/foo@2", "/bar1", "/bar2"])
1168+
_ = utils.list_files(["/bar*"])
1169+
_ = utils.list_files(["/foo@2"])
1170+
mock_tf_glob.assert_called_once_with("/bar*")
1171+
1172+
11631173

11641174
class MixtureRateTest(test_utils.FakeTaskTest):
11651175

0 commit comments

Comments
 (0)