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:
parent
8aa0c258ba
commit
fef0c05b64
8 changed files with 155 additions and 28 deletions
|
|
@ -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).
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -69824,6 +69824,42 @@
|
|||
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1682204997554" ID="ID_599701197" MODIFIED="1682205023588" TEXT="Prerequisites hinzufü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ührbar machen">
|
||||
<icon BUILTIN="flag-yellow"/>
|
||||
<node COLOR="#338800" CREATED="1682885817579" ID="ID_1080671855" MODIFIED="1682886037986" TEXT="Hash für Zeitwerte definieren">
|
||||
<richcontent TYPE="NOTE"><html>
|
||||
<head>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<p>
|
||||
Verwende eine halb-Rotation über size_t ⟹
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
die beiden Hälften des Bitstring der µ-ticks werden vertauscht
|
||||
</li>
|
||||
<li>
|
||||
damit erheblicher Abstand zwischen konsekutiven Werten
|
||||
</li>
|
||||
<li>
|
||||
hash² ≡ id
|
||||
</li>
|
||||
<li>
|
||||
impl sollte i.d.R. nach inlining eine einzige Assembler-Instruktion sein ⟶ 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äufige Impl">
|
||||
<icon BUILTIN="forward"/>
|
||||
<node CREATED="1682885740362" ID="ID_362133279" MODIFIED="1682885761537" TEXT="untere Hälfte: Seed bzw. zufällig"/>
|
||||
<node CREATED="1682885762190" ID="ID_1713133015" MODIFIED="1682885777729" TEXT="obere Hä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">
|
||||
|
|
|
|||
Loading…
Reference in a new issue