OverviewFilesCommitsIssuesDocsReleasesPeers

opencog-atomspace / opencog / atomspace / IncomeIndex.h

169 lines · c ref:
/*
 * opencog/atomspace/IncomeIndex.h
 *
 * Copyright (C) 2008,2025 Linas Vepstas <linasvepstas@gmail.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License v3 as
 * published by the Free Software Foundation and including the exceptions
 * at http://opencog.org/wiki/Licenses
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program; if not, write to:
 * Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

#ifndef _OPENCOG_INCOMEINDEX_H
#define _OPENCOG_INCOMEINDEX_H

#include <mutex>
#include <set>
#include <vector>

#include <opencog/util/oc_assert.h>
#include <opencog/atoms/base/Atom.h>
#include <opencog/atoms/base/Handle.h>
#include <opencog/atoms/atom_types/types.h>

namespace opencog
{
/** \addtogroup grp_atomspace
 *  @{
 */
// #define USE_SPARSE_IIDX 1
#if USE_SPARSE_IIDX
	typedef google::sparse_hash_map<Handle, InSetMap> InSetIdx;
#else
	typedef std::map<Handle, InSetMap> InSetIdx;
#endif

// The InSet is the IncomingSet map, plus a lock on that set.
struct InSet : InSetIdx
{
	mutable std::shared_mutex _mtx;
#if USE_SPARSE_IIDX
	InSet(void) { set_deleted_key(Handle()); }
#endif
};

#define INCOME_INDEX_SHARED_LOCK(s) std::shared_lock<std::shared_mutex> lck(s._mtx);
#define INCOME_INDEX_UNIQUE_LOCK(s) std::unique_lock<std::shared_mutex> lck(s._mtx);

/**
 * Implements a pool holding incoming sets for those Atoms that have
 * incoming sets. This pool is not currently used; it does not help
 * either with performance or with RAM usage!
 *
 * The original idea motivating this pool was the observation that
 * not all Atoms have incoming sets. Typically, only half of them do,
 * and so we should be able to save space by moving the InSetMap out
 * of the Atom, and into the pool, here. But this idea fails miserably,
 * and here's why.
 *
 * sizeof(InSetMap) = 48
 * sizeof(Handle) = 16
 * sizeof(std::pair<Handle, InSetMap>) = 64 = 16 + 48
 * sizeof std::map<Handle, InSetMap> entry approx 96 bytes total.
 *
 * The savings of reducing each Atom by 48 bytes is offset by requiring
 * approx 96 bytes to store the incoming sets for those Atoms that do
 * have them. So even if half of all Atoms don't have an incoming set,
 * there's no savings, because twice as much RAM is neded for the other
 * half.
 *
 * Measured on a typical dataset, the sfia dataset, average RAM per Atom
 * is 8 bytes **more**, using the IncomeIndex, here, than keeping the
 * incoming set inline with the Atom itself. That, plus using the index
 * here hurts performance by about 20%. In short, its a disaster. The
 * code here is left for reference, but it is not compiled.
 *
 * A pool is used to avoid lock contention. This is possible, because
 * each Handle has a deterministic hash, which can be used to identify
 * the pool.
 */
class IncomeIndex
{
	private:
		mutable std::vector<InSet> _idx;

		static constexpr int POOL_SIZE = 32;
		InSet& get_inset(const Handle& h) const
		{
			return _idx[h->get_hash() % POOL_SIZE];
		}
	public:
		IncomeIndex(void);

		void removeInset(const Handle& h)
		{
			InSet& s(get_inset(h));
			INCOME_INDEX_UNIQUE_LOCK(s);
			s.erase(h);
		}

		bool haveInset(const Handle& h) const
		{
			InSet& s(get_inset(h));
			INCOME_INDEX_SHARED_LOCK(s);
			const auto inset = s.find(h);
			return s.end() != inset;
		}

		// Get the inset, or create a new one.
		InSetMap& getInset(const Handle& h)
		{
			InSet& s(get_inset(h));
			INCOME_INDEX_UNIQUE_LOCK(s);
			InSetMap iset;

			auto iter = s.find(h);
			if (s.end() == iter)
			{
				s.insert({h, InSetMap()});
				iter = s.find(h);
			}
			return iter->second;
		}

		// Swap the handle to which the incoming set belonws.
		void swapInset(const Handle& oldh, const Handle& newh)
		{
			// Both the old and the new will have exactly the same hash.
			InSet& s(get_inset(oldh));
			INCOME_INDEX_UNIQUE_LOCK(s);
			InSetMap iset;

			auto iter = s.find(oldh);
			if (s.end() == iter)
				return;

			s.erase(oldh);
			s.insert({newh, iter->second});
		}

		// How many entries are there, anyway?
		size_t size(void) const
		{
			size_t cnt = 0;
			for (const InSet& s : _idx)
			{
				INCOME_INDEX_SHARED_LOCK(s);
				cnt += s.size();
			}
			return cnt;
		}

		void clear(void);
};

/** @}*/
} //namespace opencog

#endif // _OPENCOG_INCOMEINDEX_H