Job-Planning: base implementation of job instance creation

* using a simplified preliminary implementation of hash chaining (see #1293)
 * simplistic implementation of hashing for time values (half-rotation)
 * for now just hashing the time into the upper part of the LUID

Maybe we can even live with that implementation for some time,
depending on how important uniform distribution of hash values is
for proper usage of the frame cache.

Needless to say, various further fine points need more consideration,
especially questions of portability (32bit anyone?). Moreover, since
frame times are typically quantised, the search space for the hashed
time values is drastically reduced; conceivably we should rather
research and implement a good hash function for 128bit and then combine
all information into a single hash key....
This commit is contained in:
Fischlurch 2023-04-30 22:33:42 +02:00
parent 8aa0c258ba
commit fef0c05b64
8 changed files with 155 additions and 28 deletions

View file

@ -188,6 +188,7 @@ namespace time {
/** @internal to pass Time values to C functions */
friend gavl_time_t _raw (TimeValue const& time) { return time.t_; }
friend HashVal hash_value (TimeValue const&);
static TimeValue buildRaw_(gavl_time_t);
/** @internal diagnostics */
@ -708,6 +709,25 @@ namespace time {
/** derive a hash from the µ-tick value
* @return rotation of the raw value to produce a suitable spacing for consecutive time
* @remark picked up by Boost-hash, or std. hashtables with the help of `hash-standard.h`
* @see https://stackoverflow.com/a/31488147
*/
inline HashVal
hash_value (TimeValue const& time)
{
HashVal x = _raw(time); // possibly cap to size of hash
const uint width = sizeof(HashVal) * CHAR_BIT;
const uint mask = width-1;
const uint n = width / 2;
static_assert (0 < n and n <= mask);
return (x<<n) | (x>>((-n)&mask ));
}
/** @internal applies a limiter on the provided
* raw time value to keep it within the arbitrary
* boundaries defined by (Time::MAX, Time::MIN).

View file

@ -108,8 +108,34 @@ using lib::HashVal;
Job
JobTicket::createJobFor (FrameCoord coordinates)
{
REQUIRE (this->isValid(), "Attempt to generate render job for incomplete or unspecified render plan.");
UNIMPLEMENTED ("job planning and generation");
if (isnil (provision_))
{
}
else
{
REQUIRE (this->isValid(), "Attempt to generate render job for incomplete or unspecified render plan.");
REQUIRE (coordinates.channelNr < provision_.size(), "Inconsistent Job planning; channel beyond provision");
Provision& provision = provision_[coordinates.channelNr];
JobClosure& functor = static_cast<JobClosure&> (provision.jobFunctor); /////////////////////////TICKET #1295 : fix actual interface down to JobFunctor (after removing C structs)
Time nominalTime = coordinates.absoluteNominalTime;
InvocationInstanceID invoKey{timeHash (nominalTime, provision.invocationSeed)};
return Job(functor, invoKey, nominalTime);
}
}
/**
* Tag the precomputed invocation ID with the nominal frame time
*/
InvocationInstanceID
JobTicket::timeHash (Time nominalTime, InvocationInstanceID const& seed)
{
InvocationInstanceID res{seed};
HashVal timeMark = res.part.t;
lib::hash::combine (timeMark, hash_value (nominalTime));
res.part.t = timeMark;
return res;
}

View file

@ -61,10 +61,12 @@ namespace engine {
//using lib::time::Time;
using vault::engine::Job;
using vault::engine::JobFunctor;
using vault::engine::JobClosure; /////////////////////////////////////////////////////////////////////TICKET #1295 : fix actual interface down to JobFunctor (after removing C structs)
using lib::LinkedElements;
using lib::OrientationIndicator;
using util::isnil;
using lib::HashVal;
using lib::LUID;
//
//class ExitNode;
@ -109,12 +111,12 @@ using lib::HashVal;
{
Provision* next{nullptr};
JobFunctor& jobFunctor;
HashVal invocationSeed;
InvocationInstanceID invocationSeed;
Prerequisites requirements{};
////////////////////TODO some channel or format descriptor here
Provision (JobFunctor& func, HashVal seed =0)
: jobFunctor{func}
, invocationSeed{seed}
, invocationSeed(static_cast<JobClosure&>(func).buildInstanceID(seed)) ////////////////TICKET #1295 : fix actual interface down to JobFunctor (after removing C structs)
{ }
};
@ -159,6 +161,7 @@ using lib::HashVal;
}
protected:
static InvocationInstanceID timeHash (Time, InvocationInstanceID const&);
bool verifyInstance (JobFunctor&, Time) const;
};

View file

@ -122,8 +122,10 @@ union InvocationInstanceID
lumiera_uid luid{0};
/* ----- alternative accessors for test code ---- */
FrameCnt frameNumber;
struct {int a,b;} metaInfo;
FrameCnt frameNumber;
struct { int32_t a,b;
int64_t t;
} part;
};

View file

@ -70,6 +70,7 @@ namespace test{
checkBasicTimeValues (ref);
checkMutableTime (ref);
checkTimeHash (ref);
checkTimeConvenience (ref);
verify_invalidFramerateProtection();
createOffsets (ref);
@ -201,6 +202,25 @@ namespace test{
}
/** @test calculate a generic hash value from a time spec*/
void
checkTimeHash (TimeValue org)
{
std::hash<TimeValue> hashFunc;
CHECK (0 == hashFunc (Time::ZERO));
size_t hh = sizeof(size_t)*CHAR_BIT/2;
CHECK (size_t(1)<<hh == hashFunc (TimeValue{1}));
CHECK (size_t(1) == hashFunc (TimeValue(size_t(1)<<hh)));
size_t h1 = hashFunc (org);
size_t h2 = hashFunc (Time{org} + TimeValue{1});
size_t h3 = hashFunc (TimeValue(h1));
CHECK (h1 > 0 || org == Time::ZERO);
CHECK (h2 - h1 == size_t(1)<<hh);
CHECK (h3 == size_t(_raw(org)));
}
void
verify_invalidFramerateProtection()
{

View file

@ -87,23 +87,38 @@ namespace engine {
verify (Time nominalJobTime, InvocationInstanceID invoKey) const override
{
return Time::ANYTIME < nominalJobTime
&& 0 <= invoKey.metaInfo.a
&& invoKey.metaInfo.a < MAX_PARAM_A
&& -MAX_PARAM_B <= invoKey.metaInfo.b
&& invoKey.metaInfo.b < MAX_PARAM_B;
&& 0 <= invoKey.part.a
&& invoKey.part.a < MAX_PARAM_A
&& -MAX_PARAM_B <= invoKey.part.b
&& invoKey.part.b < MAX_PARAM_B;
}
/**
* Generate a specifically marked invocationKey for use in unit-tests.
* @remark in the actual implementation, this function generates a distinct
* base hash do tag specific processing provided by this JobFunctor;
* the seed usually factors in the media stream format; on invocation
* the nominal frame time will additionally be hashed in. Yet for
* unit testing, we typically use this dummy JobFunctor and it is
* expedient if this hash-chaining calculation is easy predictable;
* @return a zero-initialised invocationKey, assigning seed to the lower part
*/
InvocationInstanceID
buildInstanceID (HashVal seed) const override
{
UNIMPLEMENTED ("systematically generate an invoKey, distinct for the nominal time");
InvocationInstanceID res;
res.part.a = seed;
return res;
}
size_t
hashOfInstance(InvocationInstanceID invoKey) const override
hashOfInstance (InvocationInstanceID invoKey) const override
{
return std::hash<int>{} (invoKey.metaInfo.a);
} ////////////////////////////////////////////////////////////////////////TICKET #1293 : this is dangerous and could lead to clashes
std::hash<size_t> hashr;
HashVal res = hashr (invoKey.frameNumber);
lib::hash::combine (res, hashr (invoKey.part.t));
return res;
}
/* === Logging/Reporting of job invocation === */
@ -114,17 +129,17 @@ namespace engine {
TimeVar real;
int a,b;
Invocation(JobParameter param)
: nominal(TimeValue(param.nominalTime))
, real(RealClock::now())
, a(param.invoKey.metaInfo.a)
, b(param.invoKey.metaInfo.b)
Invocation (JobParameter param)
: nominal{TimeValue(param.nominalTime)}
, real{RealClock::now()}
, a{param.invoKey.part.a}
, b{param.invoKey.part.b}
{ }
Invocation()
: nominal(Time::ANYTIME)
, real(Time::NEVER)
, a(MAX_PARAM_A), b(0)
: nominal{Time::ANYTIME}
, real{Time::NEVER}
, a{MAX_PARAM_A}, b{0}
{ }
};
@ -165,8 +180,8 @@ namespace engine {
DummyJob::build()
{
InvocationInstanceID invoKey;
invoKey.metaInfo.a = rand() % MAX_PARAM_A;
invoKey.metaInfo.b = rand() % (2*MAX_PARAM_B) - MAX_PARAM_B;
invoKey.part.a = rand() % MAX_PARAM_A;
invoKey.part.b = rand() % (2*MAX_PARAM_B) - MAX_PARAM_B;
Time nominalTime = lib::test::randTime();
@ -178,8 +193,8 @@ namespace engine {
DummyJob::build (Time nominalTime, int additionalKey)
{
InvocationInstanceID invoKey;
invoKey.metaInfo.a = additionalKey;
invoKey.metaInfo.b = rand() % (2*MAX_PARAM_B) - MAX_PARAM_B;
invoKey.part.a = additionalKey;
invoKey.part.b = rand() % (2*MAX_PARAM_B) - MAX_PARAM_B;
return Job(dummyClosure, invoKey, nominalTime);
}

View file

@ -99,7 +99,7 @@ namespace test {
CHECK (hash_value(job1) != hash_value(copy)); // hash value depends on the concrete nominal job time
copy = job1;
copy.parameter.invoKey.metaInfo.a++;
copy.parameter.invoKey.part.a++;
CHECK (hash_value(job1) != hash_value(copy)); // hash value depends on the internal interpretation of the invocation key
@ -115,7 +115,7 @@ namespace test {
size_t
hashOfInstance(InvocationInstanceID invoKey) const
{
return boost::hash_value (invoKey.metaInfo.a);
return boost::hash_value (invoKey.part.a);
}
};
OtherClosure someOtherClosure;

View file

@ -69824,6 +69824,42 @@
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1682204997554" ID="ID_599701197" MODIFIED="1682205023588" TEXT="Prerequisites hinzuf&#xfc;gen">
<icon BUILTIN="flag-yellow"/>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1682868258074" ID="ID_1743148920" MODIFIED="1682868285655" TEXT="Job aus Ticket erstellen und ausf&#xfc;hrbar machen">
<icon BUILTIN="flag-yellow"/>
<node COLOR="#338800" CREATED="1682885817579" ID="ID_1080671855" MODIFIED="1682886037986" TEXT="Hash f&#xfc;r Zeitwerte definieren">
<richcontent TYPE="NOTE"><html>
<head>
</head>
<body>
<p>
Verwende eine halb-Rotation &#252;ber size_t &#10233;
</p>
<ul>
<li>
die beiden H&#228;lften des Bitstring der &#181;-ticks werden vertauscht
</li>
<li>
damit erheblicher Abstand zwischen konsekutiven Werten
</li>
<li>
hash&#178; &#8801; id
</li>
<li>
impl sollte i.d.R. nach inlining eine einzige Assembler-Instruktion sein &#10230; https://stackoverflow.com/a/31488147
</li>
</ul>
</body>
</html></richcontent>
<icon BUILTIN="button_ok"/>
</node>
<node COLOR="#338800" CREATED="1682885599653" ID="ID_1110668206" LINK="#ID_444576509" MODIFIED="1682885796274" TEXT="erster Entwurf der Hash-Verkettung">
<icon BUILTIN="button_ok"/>
</node>
<node COLOR="#338800" CREATED="1682885673339" ID="ID_1525753166" MODIFIED="1682885692857" TEXT="Instance-Hash: hier den Seed einfach durchreichen">
<icon BUILTIN="button_ok"/>
</node>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1682627654491" ID="ID_1371147624" MODIFIED="1682627673234" TEXT="Marker definieren und sichtbar machen">
<linktarget COLOR="#48417c" DESTINATION="ID_1371147624" ENDARROW="Default" ENDINCLINATION="-48;56;" ID="Arrow_ID_1753796050" SOURCE="ID_1110039315" STARTARROW="None" STARTINCLINATION="-284;-8;"/>
<icon BUILTIN="flag-yellow"/>
@ -70696,6 +70732,11 @@
<node BACKGROUND_COLOR="#f0d5c5" COLOR="#990033" CREATED="1681594271596" ID="ID_963803611" MODIFIED="1681594283643" TEXT="in welchem Scope ist sie eindeutig?">
<icon BUILTIN="help"/>
</node>
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#690f14" CREATED="1682885727188" ID="ID_444576509" MODIFIED="1682885734939" TEXT="vorl&#xe4;ufige Impl">
<icon BUILTIN="forward"/>
<node CREATED="1682885740362" ID="ID_362133279" MODIFIED="1682885761537" TEXT="untere H&#xe4;lfte: Seed bzw. zuf&#xe4;llig"/>
<node CREATED="1682885762190" ID="ID_1713133015" MODIFIED="1682885777729" TEXT="obere H&#xe4;lfte: Hash aus der nominellen Zeit"/>
</node>
</node>
<node CREATED="1682039636789" ID="ID_986989156" MODIFIED="1682039638216" TEXT="Job">
<node CREATED="1682040113581" ID="ID_226959477" MODIFIED="1682040120147" TEXT="Bestandteile">