2 +----------------------------------------------------------------------+
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010- Facebook, Inc. (http://www.facebook.com) |
6 +----------------------------------------------------------------------+
7 | This source file is subject to version 3.01 of the PHP license, |
8 | that is bundled with this package in the file LICENSE, and is |
9 | available through the world-wide-web at the following url: |
10 | http://www.php.net/license/3_01.txt |
11 | If you did not receive a copy of the PHP license and are unable to |
12 | obtain it through the world-wide-web, please send a note to |
13 | license@php.net so we can mail you a copy immediately. |
14 +----------------------------------------------------------------------+
16 #include "hphp/runtime/vm/type_profile.h"
17 #include "hphp/runtime/base/runtime_option.h"
18 #include "hphp/runtime/base/stats.h"
19 #include "hphp/runtime/vm/translator/translator.h"
20 #include "hphp/util/trace.h"
23 #include "sys/types.h"
31 TRACE_SET_MOD(typeProfile
);
34 * It is useful at translation time to have a hunch as to the types a given
35 * instruction is likely to produce. This is a probabilistic data structure
36 * that tries to balance: cost of reading and writing; memory overhead;
37 * prediction recall (percentage of time we make a prediction); prediction
38 * accuracy (fidelity relative to reality); and prediction precision
39 * (fidelity relative to ourselves).
42 typedef uint16_t Counter
;
43 static const Counter kMaxCounter
= USHRT_MAX
;
47 // All of these saturate at 255.
48 Counter m_totalSamples
;
49 Counter m_samples
[MaxNumDataTypesIndex
];
51 for (int i
= 0; i
< MaxNumDataTypesIndex
; i
++) {
52 TRACE(0, "t%3d: %4d\n", i
, m_samples
[getDataTypeValue(i
)]);
64 * Tradeoff: size vs. accuracy.
68 * Size: (sizeof(ValueProfile) == 16) B -> 4MB
70 * Accuracy: If we collide further than kLineSize, we toss out perfectly
71 * good evidence. www seems to use about 12000 method names at this
72 * writing, so we have a decent pad for collisions.
75 static const int kNumEntries
= 1 << 18;
76 static const int kLineSizeLog2
= 2;
77 static const int kLineSize
= 1 << kLineSizeLog2
;
78 static const int kNumLines
= kNumEntries
/ kLineSize
;
79 static const int kNumLinesMask
= kNumLines
- 1;
84 * Tradeoff: recall vs. accuracy. This is the minimum number of examples
85 * before we'll report any information at all.
87 * Recall: If we set this too high, we'll never be able to predict
90 * Accuracy: If we set this too low, we'll be "jumpy", eagerly predicting
91 * types on the basis of weak evidence.
93 static const double kMinInstances
= 99.0;
95 typedef ValueProfile ValueProfileLine
[kLineSize
];
96 static ValueProfileLine
* profiles
;
98 static ValueProfileLine
*
100 const std::string
& path
= RuntimeOption::EvalJitProfilePath
;
105 TRACE(1, "profileInit: path %s\n", path
.c_str());
106 int fd
= open(path
.c_str(), O_RDWR
| O_CREAT
, 0600);
108 TRACE(0, "profileInit: open %s failed: %s\n", path
.c_str(),
114 size_t len
= sizeof(ValueProfileLine
) * kNumLines
;
115 int retval
= ftruncate(fd
, len
);
118 TRACE(0, "profileInit: truncate %s failed: %s\n", path
.c_str(),
123 int flags
= PROT_READ
|
124 (RuntimeOption::EvalJitProfileRecord
? PROT_WRITE
: 0);
125 void* mmapRet
= mmap(0, len
, flags
, MAP_SHARED
, // Yes, shared.
127 if (mmapRet
== MAP_FAILED
) {
129 TRACE(0, "profileInit: mmap %s failed: %s\n", path
.c_str(),
133 return (ValueProfileLine
*)mmapRet
;
139 profiles
= profileInitMmap();
141 TRACE(1, "profileInit: anonymous memory.\n");
142 profiles
= (ValueProfileLine
*)calloc(sizeof(ValueProfileLine
), kNumLines
);
151 * In cli mode, we only record samples if we're in recording to replay
154 * For server mode, we record samples for all requests started after
155 * the EvalJitWarmupRequests'th req.
157 bool __thread profileOn
= false;
158 static int64_t numRequests
;
160 static inline bool warmedUp() {
161 return (numRequests
>= RuntimeOption::EvalJitWarmupRequests
) ||
162 (RuntimeOption::clientExecutionMode() &&
163 !RuntimeOption::EvalJitProfileRecord
);
166 static inline bool profileThisRequest() {
167 if (warmedUp()) return false;
168 if (RuntimeOption::serverExecutionMode()) return true;
169 return RuntimeOption::EvalJitProfileRecord
;
173 profileRequestStart() {
174 bool p
= profileThisRequest();
175 if (p
!= profileOn
) {
177 if (!ThreadInfo::s_threadInfo
.isNull()) {
178 ThreadInfo::s_threadInfo
->m_reqInjectionData
.updateJit();
183 void profileRequestEnd() {
184 numRequests
++; // racy RMW; ok to miss a rare few.
192 TypeProfileKey::hash() const {
193 return hash_int64_pair(m_kind
, m_name
->hash());
196 static inline ValueProfile
*
197 keyToVP(TypeProfileKey key
, KeyToVPMode mode
) {
199 uint64_t h
= key
.hash();
200 // Use the low-order kLineSizeLog2 bits to as tag bits to distinguish
201 // within the line, rather than to choose a line. Without the shift, all
202 // the tags in the line would have the same low-order bits, making
203 // collisions kLineSize times more likely.
204 int hidx
= (h
>> kLineSizeLog2
) & kNumLinesMask
;
205 ValueProfileLine
& l
= profiles
[hidx
];
206 int replaceCandidate
= 0;
208 for (int i
= 0; i
< kLineSize
; i
++) {
209 if (l
[i
].m_tag
== uint32_t(h
)) {
210 TRACE(2, "Found %d for %s -> %d\n",
211 l
[i
].m_tag
, key
.m_name
->data(), uint32_t(h
));
214 if (mode
== Write
&& l
[i
].m_totalSamples
< minCount
) {
215 replaceCandidate
= i
;
216 minCount
= l
[i
].m_totalSamples
;
220 assert(replaceCandidate
>= 0 && replaceCandidate
< kLineSize
);
221 ValueProfile
& vp
= l
[replaceCandidate
];
222 Stats::inc(Stats::TypePred_Evict
, vp
.m_totalSamples
!= 0);
223 Stats::inc(Stats::TypePred_Insert
);
224 TRACE(1, "Killing %d in favor of %s -> %d\n",
225 vp
.m_tag
, key
.m_name
? key
.m_name
->data() : "NULL", uint32_t(h
));
226 vp
.m_totalSamples
= 0;
227 memset(&vp
.m_samples
, 0, sizeof(vp
.m_samples
));
228 // Zero first, then claim. It seems safer to temporarily zero out some
229 // other function's values than to have this new function using the
230 // possibly-non-trivial prediction from an unrelated function.
231 Util::compiler_membar();
232 vp
.m_tag
= uint32_t(h
);
233 return &l
[replaceCandidate
];
238 void recordType(TypeProfileKey key
, DataType dt
) {
239 if (!profiles
) return;
240 if (!shouldProfile()) return;
241 assert(dt
!= KindOfUninit
);
242 // Normalize strings to KindOfString.
243 if (dt
== KindOfStaticString
) dt
= KindOfString
;
244 TRACE(1, "recordType lookup: %s -> %d\n", key
.m_name
->data(), dt
);
245 ValueProfile
*prof
= keyToVP(key
, Write
);
246 if (prof
->m_totalSamples
!= kMaxCounter
) {
247 prof
->m_totalSamples
++;
248 // NB: we can't quite assert that we have fewer than kMaxCounter samples,
249 // because other threads are updating this structure without locks.
250 int dtIndex
= getDataTypeIndex(dt
);
251 if (prof
->m_samples
[dtIndex
] < kMaxCounter
) {
252 prof
->m_samples
[dtIndex
]++;
255 ONTRACE(2, prof
->dump());
258 typedef std::pair
<DataType
, double> PredVal
;
260 PredVal
predictType(TypeProfileKey key
) {
261 PredVal kNullPred
= std::make_pair(KindOfUninit
, 0.0);
262 if (!profiles
) return kNullPred
;
263 const ValueProfile
*prof
= keyToVP(key
, Read
);
265 TRACE(2, "predictType lookup: %s -> MISS\n", key
.m_name
->data());
266 Stats::inc(Stats::TypePred_Miss
);
269 double total
= prof
->m_totalSamples
;
270 if (total
< kMinInstances
) {
271 Stats::inc(Stats::TypePred_MissTooFew
);
272 TRACE(2, "TypePred: hit %s but too few samples numSamples %d\n",
273 key
.m_name
->data(), prof
->m_totalSamples
);
276 double maxProb
= 0.0;
277 DataType pred
= KindOfUninit
;
278 // If we have fewer than kMinInstances predictions, consider it too
279 // little data to be actionable.
280 for (int i
= 0; i
< MaxNumDataTypesIndex
; ++i
) {
281 double prob
= (1.0 * prof
->m_samples
[i
]) / total
;
282 if (prob
> maxProb
) {
284 pred
= getDataTypeValue(i
);
286 if (prob
>= 1.0) break;
288 Stats::inc(Stats::TypePred_Hit
, maxProb
>= 1.0);
289 Stats::inc(Stats::TypePred_MissTooWeak
, maxProb
< 1.0);
290 TRACE(2, "TypePred: hit %s numSamples %d pred %d prob %g\n",
291 key
.m_name
->data(), prof
->m_totalSamples
, pred
, maxProb
);
292 // Probabilities over 1.0 are possible due to racy updates.
293 if (maxProb
> 1.0) maxProb
= 1.0;
294 return std::make_pair(pred
, maxProb
);
297 bool isProfileOpcode(const PC
& pc
) {
298 return *pc
== OpRetC
|| *pc
== OpCGetM
;