Skip to content

Instantly share code, notes, and snippets.

@mocurin
Last active May 25, 2019 16:22
Show Gist options
  • Save mocurin/d8f52ec869548cd064306fcc7f62dfe6 to your computer and use it in GitHub Desktop.
Save mocurin/d8f52ec869548cd064306fcc7f62dfe6 to your computer and use it in GitHub Desktop.
ТИМП РК

Использование АТД в реальных проектах

Для начала стоит отметить, что проекты я выбирал на си-подобных языках: в первую очередь С++, Java и прочие. Стратегией поиска были: форк репозитория, клонирование его на виртуальную машину под OC linux и поиск по содержимому файлов с помощью команды grep --include=\*.{cpp,hpp} -rnw '/path/to/somewhere/' -e "pattern" Где в фигурных скобках указываются типы файлов, которые нужно проверять, в одинарных кавычках - путь, в двойных - то, что нужно искать в этих файлах. К примеру, в проекте Aleth (C++ Etherium client):

Результат поиска по запросу list
mocurin@mocurin-VirtualBox:~$ grep --include=\*.{cpp,hpp} -rnw 'aleth' -e "list"
aleth/libweb3jsonrpc/Eth.cpp:144:	//Return list of transaction that being sent by local accounts
aleth/libdevcrypto/LibSnark.cpp:133:	// Input: list of pairs of G1 and G2 points
aleth/libevm/VMFactory.cpp:54:/// We don't use a map to avoid complex dynamic initialization. This list will never be long,
aleth/libevm/VMFactory.cpp:115:/// The list of EVMC options stored as pairs of (name, value).
aleth/rlp/main.cpp:205:        else if (arg == "list")
aleth/rlp/main.cpp:220:             << "    list     [ <file> | -- ]  List the items in the RLP list by hash and size." << endl
aleth/rlp/main.cpp:221:             << "    extract  [ <file> | -- ]  Extract all items in the RLP list, named by hash." << endl
aleth/rlp/main.cpp:335:                cerr << "Error: Invalid format; RLP data is not a list." << endl;
aleth/rlp/main.cpp:343:                    cerr << "Error: Invalid format; RLP list item is not data." << endl;
aleth/rlp/main.cpp:355:                cerr << "Error: Invalid format; RLP data is not a list." << endl;
aleth/rlp/main.cpp:363:                    cerr << "Error: Invalid format; RLP list item is not data." << endl;
aleth/libethereum/EthereumCapability.cpp:449:            // Build list of peers to send transactions to
aleth/libethereum/BlockQueue.cpp:318:    list<h256> badQueue(1, _bad);
aleth/libethereum/BlockQueue.cpp:476:    list<h256> goodQueue(1, _good);
aleth/libethereum/BlockChainSync.cpp:928:    // Can't check invariants here since the peers is already removed from the list and the state is not updated yet.
aleth/libethcore/BlockHeader.cpp:160:        BOOST_THROW_EXCEPTION(InvalidBlockFormat() << errinfo_comment("Block must be a list") << BadFieldError(0, _block.toString()));
aleth/libethcore/BlockHeader.cpp:163:        BOOST_THROW_EXCEPTION(InvalidBlockFormat() << errinfo_comment("Block header must be a list") << BadFieldError(0, header.data().toString()));
aleth/libethcore/BlockHeader.cpp:165:        BOOST_THROW_EXCEPTION(InvalidBlockFormat() << errinfo_comment("Block transactions must be a list") << BadFieldError(1, root[1].data().toString()));
aleth/libethcore/BlockHeader.cpp:167:        BOOST_THROW_EXCEPTION(InvalidBlockFormat() << errinfo_comment("Block uncles must be a list") << BadFieldError(2, root[2].data().toString()));
aleth/libethcore/TransactionBase.cpp:53:			BOOST_THROW_EXCEPTION(InvalidTransactionFormat() << errinfo_comment("transaction RLP must be a list"));
aleth/libp2p/NodeTable.cpp:157:list<NodeID> NodeTable::nodes() const
aleth/libp2p/NodeTable.cpp:159:    list<NodeID> nodes;
aleth/libp2p/NodeTable.cpp:168:list<NodeEntry> NodeTable::snapshot() const
aleth/libp2p/NodeTable.cpp:170:    list<NodeEntry> ret;
aleth/libp2p/NodeTable.cpp:377:                // (i.e. to the end of the list)
aleth/libp2p/Host.cpp:764:    list<shared_ptr<Peer>> toConnect;
aleth/libp2p/Host.cpp:920:    list<NodeEntry> nodeTableEntries;
aleth/libp2p/Host.cpp:1014:                // node was saved from the connected peer list
aleth/test/tools/fuzzTesting/fuzzHelper.cpp:129:			//make header as list
aleth/test/tools/fuzzTesting/fuzzHelper.cpp:200:			//list 0-55 [0xc0, 0xf7] + data
aleth/test/tools/fuzzTesting/fuzzHelper.cpp:209:			//list more 55 [0xf8, 0xff] + length + data
aleth/test/tools/fuzzTesting/fuzzHelper.cpp:430:	randomAddressProbability(3),		//probability of generating a random address instead of defined from list
aleth/test/tools/fuzzTesting/fuzzHelper.cpp:575:		// Return address of the sender (sender is  a part of state acount list)
aleth/test/tools/jsontests/BlockChainTests.cpp:489:        // if exception is thrown, RLP is invalid and no blockHeader, Transaction list, or Uncle list should be given
aleth/test/tools/jsontests/BlockChainTests.cpp:865:        cnote << "uncle list mismatch\n" << RLP(_a.bytes())[2].data() << "\n" << RLP(_b.bytes())[2].data();
aleth/test/tools/jsontests/BlockChainTests.cpp:941:    // if exception is thrown, RLP is invalid and no blockHeader, Transaction list, or Uncle list should be given
aleth/test/tools/jsontests/BlockChainTests.cpp:974:    BOOST_CHECK_MESSAGE(txsFromRlp.size() == txsFromField.size(), _testname + "transaction list size does not match");
aleth/test/tools/libtesteth/TestSuite.cpp:43:		list<string> removeList;
aleth/test/tools/libtesteth/Options.cpp:76:    cout << setw(30) << "--help" << setw(25) << "Display list of command arguments\n";
aleth/test/unittests/libethereum/TransactionQueue.cpp:272:    //future list size is now 3  + 1 imported transaction
aleth/test/unittests/libp2p/peer.cpp:150:    list<Host*> hosts;
aleth/test/unittests/libp2p/net.cpp:617:    list<NodeEntry> const allNodeEntries = nodeTable->snapshot();
aleth/test/unittests/libdevcore/RLP.cpp:79:    // non-list RLP data
aleth/aleth/AccountManager.cpp:33:    _out << "   account list                                List all keys available in wallet\n"
aleth/aleth/AccountManager.cpp:80:		if (argc < 3 || string(argv[2]) == "list")
aleth/aleth/main.cpp:285:    peersetDescriptionStream << "Comma delimited list of peers; element format: type:enode://publickey@ipAddress[:port[?discport=port]]\n"
aleth/aleth/main.cpp:295:    addNetworkingOption("peerset", po::value<string>()->value_name("<list>"), peersetDescription.c_str());
aleth/libdevcore/RLP.cpp:251:            size_t s = m_out.size() - p;		// list size
aleth/libdevcore/RLP.cpp:270:        _itemCount = 1;	// for all following iterations, we've effectively appended a single item only since we completed a list.
aleth/libdevcore/DBFactory.cpp:47:/// We don't use a map to avoid complex dynamic initialization. This list will never be long,
aleth/libdevcore/LoggingProgramOptions.cpp:33:        "Space-separated list of the log channels to show (default: show all channels)." +
aleth/libdevcore/LoggingProgramOptions.cpp:44:        "Space-separated list of the log channels to hide.\n");
aleth/evmc/include/evmc/evmc.hpp:83:    /// with provided list of options.

Очередь

aleth/libethereum/BlockQueue.cpp:318:    list<h256> badQueue(1, _bad);
aleth/libethereum/BlockQueue.cpp:476:    list<h256> goodQueue(1, _good);

Код метода выглядит так:

void BlockQueue::noteReady_WITH_LOCK(h256 const& _good)
{
	DEV_INVARIANT_CHECK;
	list<h256> goodQueue(1, _good);  // 1
	bool notify = false;
	while (!goodQueue.empty())
	{
		h256 const parent = goodQueue.front();  // 2
		vector<pair<h256, bytes>> const removed = m_unknown.removeByKeyEqual(parent);
		goodQueue.pop_front();  // 3
		for (auto& newReady: removed)
		{
			DEV_GUARDED(m_verification)
			m_unverified.enqueue(UnverifiedBlock { newReady.first, parent, move(newReady.second) });
			m_unknownSet.erase(newReady.first);
			m_readySet.insert(newReady.first);
			goodQueue.push_back(newReady.first);  // 4
			notify = true;
		}
	}
	if (notify)
	m_moreToVerify.notify_all();
}

Я отметил в коде 4 места:

  1. Строка 476: создание "очереди", которая по факту очередью не является. Здесь используется STL-ный двунаправленный список "std::list", но используется он лишь в рамках функционала очереди.
  2. Строка 480: доступ к первому элементу. Здесь вызывается метод list::front(), позволяющий "взглянуть" на первый элемент.
  3. Строка 482: удаление первого элемента из очереди. Метод list::pop_front() удаляет "голову" списка, то есть первый элемент.
  4. Строка 489: добавление элемента в очередь. Метод list::push_back(Elem elem) добавляет elem в конец списка.

Касаемо очистки списка - тут она происходит в деструкторе std::list, который вызывается в конце блока, куда помещен этот список,- память выделенная под каждый элемент освобождается и список таким образом очищается.

Таким образом, std::list здесь используется как очередь: элементы забираются из начала, добавляются лишь в конец. Реализуется FIFO (first in first out). В STL есть достаточно удобный для этого контейнер std::queue, но, возможно, в силу его реализации, разработчики этого проекта предпочли двунаправленный список.

Список

В проекте Aleth достаточно трудно найти место, где на списке что-то по-настоящему базируется. В основном там используются хэш-таблицы. Поэтому я выбрал другой проект - Thor-os: Код там написан достаточно просто и, что самое главное, там есть юнит-тесты, где большая часть методов успешно вызывается. Файл thor-os/tstl/test_suite/vector.cpp

  1. Строка 30: Создание вектора. Как действие над atd мы это не обозначали, но отметить это место как важное считаю нужным
void test_erase(){
	std::vector<int> a{1, 0, 0, 2, 3, 4};
	...
  1. Строка 32: Удаление элемента. Здесь происходит удаление по индексу, т.е удаляется третий по счету элемент.
	...
	a.erase(2);
	...
  1. Строка 34: Получение размера списка.
	...
	check(a.size() == 5, "Invalid vector:size");
	...
  1. Строка 35: Получение i-го элемента, т.е. получение элемента по индексу (std::vector::operator[](size_t index))
	...
	check(a[0] == 1, "Invalid vector:[]");
	...
  1. Строка 176: Очистка списка.
	...
	std::vector<kiss> vec;
	...
	vec.clear();
	...
  1. Строки 198 и 202: Вставка элементов. В то время как push_back является частным случаем вставки, который для std::vector имеет амортизированную сложность O(1), метод push_front для вектора несильно отличается от std::vector::insert(Elem elem), поэтому приведу я только их
	...
	for(size_t i = 0; i < 1000; ++i){
		vec.push_back(i);
	}

	for(size_t i = 10000; i < 11000; ++i){
		vec.push_front(i);
	}
	...
  1. Строка 121: Получение следующего элемента. Здесь это действие реализовано через инкремент итератора. С помощью декремента, аналогичной операции, можно получить предыдущий элемент
	...
	auto it = a.begin();
	...
	++it;
	...

Эти методы используются и в других местах, но как правило, использованы через итераторы, а не через индексы, а потому менее явны.

Дерево

В ранее рассмотренном проекте Aleth так же есть структура trie, которая является деревом. В основном ее разные формы(например с хэшированием или без), реализуется в файле aleth/libdevcore/TrieDB.h

  1. Строки 785, 786, файла aleth/libethereum/State.cpp. Получение корня
	SecureTrieDB<h256, DB> storageDB(_state.db(), i.second.baseRoot())
	...
	assert(storageDB.root());
	s.append(storageDB.root());
	...

Здесь корень сначала проверяется на отсутствие нуля, а затем помещается в поток s.

Так же, в проекте есть RangeMask - структура, которая, насколько я понимаю, нужна для хранения и слияния ренджей итераторов, внутри которого используется std::map, являющаяся деревом. Далее операции будут производиться над полем m_ranges std::map<unsigned, unsigned> m_ranges;

  1. Строка 69, файла aleth/libdevcore/RangeMask.h. Получение самого левого сына
	...
	for (auto i = m_ranges.begin(); i != m_ranges.end() && _items; ++i)
	...

Зная строение std::map можно без труда определить, что получение начального итератора и будет реализовывать это действие.

  1. Строка 84, того же файла. Получение элемента
...
for (auto i: m_ranges)
    ...
	if (i.first != last)
		ret.m_ranges[last] = i.first;
	last = i.second;
	...

.first и .last - получение значений пары, содержащейся в каждом ноде дерева. Строки вытащены из метода RangeMask inverted() const, согласно комментарию возвращет дополнение входящих ренджей

  1. Строка 141, того же файла. Обнуление дерева
void clear()
{
	m_ranges.clear();
}

Используется для очистки содержимого управляющей структуры.

  1. Слияние и получение правого брата Конкретно в этом проекте мне не удалось найти каких-то операций ни одного, ни другого в явном типе. Слияние реализуется последовательной вставкой одного контейнера в другой, "быстрое" слияние не используется, либо просто не реализуется.

Множество

Так же в Aleth есть достаточно много примеров использования std::unordered_set как примера типичного множества:

  1. Строка 330, файл aleth/libdevcore/CommonData.h. Проверка принадлежности элемента множеству
template <class V>
bool contains(std::unordered_set<V> const& _set, V const& _v)
{
	return _set.find(_v) != _set.end();
}

Применяется, например в файле aleth/libdevcore/Log.cpp:

	...
	contains(_options.includeChannels, messageChannel)) &&
		!contains(_options.excludeChannels, messageChannel);
	...

includeChannels и excludeChannels являются std::unordered_set для определенного в проекте класса channel.

  1. Строка 273, файл aleth/libdevcore/CommonData.h. Математические операции с множествами, добавление элемента в множество и слияние множеств
template <class T, class U>
std::unordered_set<T>& operator+=(std::unordered_set<T>& _a, U const& _b)
{
	_a.insert(std::begin(_b), std::end(_b));
	return _a;
}

Приведу определение, но не использование функции, т.к. функция - оператор, которые куда труднее искать в проекте чем обычные функции. Так же этот оператор объявлен для большинства других контейнеров в проекте. Внутри функции присутствует вставка - метод std::unordered_set::insert(It start, it end)

Слияние так же реализуется это операцией. Как правило, множества сливаются в одно существующее или последовательно в созданное заранее.

  1. Строка 70, файла aleth/libethereum/EthereumPeer.h. Обнуление множества.
void clearKnownBlocks() { m_knownBlocks.clear(); }

Поле m_knownBlocks объявлено как h256Hash m_knownBlocks , а h256Hash, в свою очередь using h256Hash = std::unordered_set<h256>. Таким образом это все то же множество. clearKnownBlocks() используется в одном файле проекта (aleth/libethereum/EthereumCapability.cpp) для очистки множества.

  1. Строка 273, файла aleth/libethereum/ClientBase.cpp. Удаление элемента из множества
	...
	auto fit = m_filters.find(id);
	...
	m_filters.erase(fit);
	...

Здесь fit итератор std::unordered_map - т.е словаря m_filters. Происходит удаление по итератору.

  1. Касаемо нахождения минимального элемента - конкретно в этом проекте это не используется. Как правило, этот АТД используется с такими типами в качестве элементов, что в этой операции нет необходимости

Выводы

В общем и целом - эти типы данных принимают самые разные формы, адаптируясь под цели проекта. Они могут как реализовывать функции своего типа, так и пропускать некоторые за ненадобностью. Даже если все методы реализуются, они могут быть не доступны пользователю Так же, как мне кажется, границы между ними порой очень размываются и определить что где и как используется достаточно трудно

Таким образом, АТД используются повсеместно, но чтобы такое использование приносило только пользу необходимо правильно их подбирать

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment