ArmNN
 21.08
Profiling.cpp
Go to the documentation of this file.
1 //
2 // Copyright © 2017 Arm Ltd. All rights reserved.
3 // SPDX-License-Identifier: MIT
4 //
5 #include "Profiling.hpp"
6 
7 #include <armnn/BackendId.hpp>
9 
10 #include "JsonPrinter.hpp"
11 
12 #if ARMNN_STREAMLINE_ENABLED
13 #include <streamline_annotate.h>
14 #endif
15 
16 #include <algorithm>
17 #include <iomanip>
18 #include <iostream>
19 #include <fstream>
20 #include <map>
21 #include <stack>
22 
23 namespace armnn
24 {
25 
26 // Controls the amount of memory initially allocated to store profiling events.
27 // If chosen carefully, the profiling system will not make any additional allocations, thus minimizing its impact on
28 // measured times.
29 constexpr std::size_t g_ProfilingEventCountHint = 1024;
30 
31 // Whether profiling reports should include the sequence of events together with their timings.
32 constexpr bool g_WriteProfilingEventSequence = true;
33 
34 // Whether profiling reports should also report detailed information on events grouped by inference.
35 // This can spam the output stream, so use carefully (or adapt the code to just output information
36 // of interest).
38 
39 // Whether a call to Profiler::AnalyzeEventsAndWriteResults() will be made when the Profiler is destroyed.
40 // It can be convenient for local tests.
42 
43 Measurement FindMeasurement(const std::string& name, const Event* event)
44 {
45 
46  ARMNN_ASSERT(event != nullptr);
47 
48  // Search though the measurements.
49  for (const auto& measurement : event->GetMeasurements())
50  {
51  if (measurement.m_Name == name)
52  {
53  // Measurement found.
54  return measurement;
55  }
56  }
57 
58  // Measurement not found.
59  return Measurement{ "", 0.f, Measurement::Unit::TIME_MS };
60 }
61 
62 std::vector<Measurement> FindKernelMeasurements(const Event* event)
63 {
64  ARMNN_ASSERT(event != nullptr);
65 
66  std::vector<Measurement> measurements;
67 
68  // Search through the measurements.
69  for (const auto& measurement : event->GetMeasurements())
70  {
71  if (measurement.m_Name.rfind("OpenClKernelTimer", 0) == 0
72  || measurement.m_Name.rfind("NeonKernelTimer", 0) == 0)
73  {
74  // Measurement found.
75  measurements.push_back(measurement);
76  }
77  }
78 
79  return measurements;
80 }
81 
82 std::map<std::string, ProfilerImpl::ProfilingEventStats> ProfilerImpl::CalculateProfilingEventStats() const
83 {
84  std::map<std::string, ProfilingEventStats> nameToStatsMap;
85 
86  for (const auto& event : m_EventSequence)
87  {
89 
90  double durationMs = measurement.m_Value;
91  auto it = nameToStatsMap.find(event->GetName());
92  if (it != nameToStatsMap.end())
93  {
94  ProfilingEventStats& stats = it->second;
95  stats.m_TotalMs += durationMs;
96  stats.m_MinMs = std::min(stats.m_MinMs, durationMs);
97  stats.m_MaxMs = std::max(stats.m_MaxMs, durationMs);
98  ++stats.m_Count;
99  }
100  else
101  {
102  nameToStatsMap.emplace(event->GetName(), ProfilingEventStats{ durationMs, durationMs, durationMs, 1 });
103  }
104  }
105 
106  return nameToStatsMap;
107 }
108 
109 const Event* GetEventPtr(const Event* ptr) { return ptr;}
110 const Event* GetEventPtr(const std::unique_ptr<Event>& ptr) {return ptr.get(); }
111 
112 template<typename ItertType>
113 void ProfilerImpl::AnalyzeEventSequenceAndWriteResults(ItertType first, ItertType last, std::ostream& outStream) const
114 {
115  // Outputs event sequence, if needed.
116  if (g_WriteProfilingEventSequence)
117  {
118  // Makes sure timestamps are output with 6 decimals, and save old settings.
119  std::streamsize oldPrecision = outStream.precision();
120  outStream.precision(6);
121  std::ios_base::fmtflags oldFlags = outStream.flags();
122  outStream.setf(std::ios::fixed);
123  // Outputs fields.
124  outStream << "Event Sequence - Name | Duration (ms) | Start (ms) | Stop (ms) | Device" << std::endl;
125  for (auto event = first; event != last; ++event)
126  {
127  const Event* eventPtr = GetEventPtr((*event));
128  double startTimeMs = FindMeasurement(WallClockTimer::WALL_CLOCK_TIME_START, eventPtr).m_Value;
129  double stopTimeMs = FindMeasurement(WallClockTimer::WALL_CLOCK_TIME_STOP, eventPtr).m_Value;
130 
131  // Find the WallClock measurement if there is one.
132  double durationMs = FindMeasurement(WallClockTimer::WALL_CLOCK_TIME, eventPtr).m_Value;
133  outStream << std::setw(50) << eventPtr->GetName() << " "
134  << std::setw(20) << durationMs
135  << std::setw(20) << startTimeMs
136  << std::setw(20) << stopTimeMs
137  << std::setw(20) << eventPtr->GetBackendId().Get()
138  << std::endl;
139  }
140  outStream << std::endl;
141  // Restores previous precision settings.
142  outStream.flags(oldFlags);
143  outStream.precision(oldPrecision);
144  }
145 
146  // Aggregates results per event name.
147  std::map<std::string, ProfilingEventStats> nameToStatsMap = CalculateProfilingEventStats();
148 
149  // Outputs aggregated stats.
150  outStream << "Event Stats - Name | Avg (ms) | Min (ms) | Max (ms) | Total (ms) | Count" << std::endl;
151  for (const auto& pair : nameToStatsMap)
152  {
153  const std::string& eventLabel = pair.first;
154  const ProfilingEventStats& eventStats = pair.second;
155  const double avgMs = eventStats.m_TotalMs / double(eventStats.m_Count);
156 
157  outStream << "\t" << std::setw(50) << eventLabel << " " << std::setw(9) << avgMs << " "
158  << std::setw(9) << eventStats.m_MinMs << " " << std::setw(9) << eventStats.m_MaxMs << " "
159  << std::setw(9) << eventStats.m_TotalMs << " " << std::setw(9) << eventStats.m_Count << std::endl;
160  }
161  outStream << std::endl;
162 }
163 
165  : m_ProfilingEnabled(false),
167 {
168  m_EventSequence.reserve(g_ProfilingEventCountHint);
169 
170 #if ARMNN_STREAMLINE_ENABLED
171  // Initialises streamline annotations.
172  ANNOTATE_SETUP;
173 #endif
174 }
175 
177 {
178  if (m_ProfilingEnabled)
179  {
180  if (g_WriteReportToStdOutOnProfilerDestruction)
181  {
182  Print(std::cout);
183  }
184  }
185 
186  // Un-register this profiler from the current thread.
188 }
189 
191 {
192  return m_ProfilingEnabled;
193 }
194 
195 void ProfilerImpl::EnableProfiling(bool enableProfiling)
196 {
197  m_ProfilingEnabled = enableProfiling;
198 }
199 
201 {
203 }
204 
206  const BackendId& backendId,
207  const std::string& label,
208  std::vector<InstrumentPtr>&& instruments,
210 {
211  Event* parent = m_Parents.empty() ? nullptr : m_Parents.top();
212  m_EventSequence.push_back(std::make_unique<Event>(label,
213  profiler,
214  parent,
215  backendId,
216  std::move(instruments),
217  guid));
218  Event* event = m_EventSequence.back().get();
219  event->Start();
220 
221 #if ARMNN_STREAMLINE_ENABLED
222  ANNOTATE_CHANNEL_COLOR(uint32_t(m_Parents.size()), GetEventColor(backendId), label.c_str());
223 #endif
224 
225  m_Parents.push(event);
226  return event;
227 }
228 
230 {
231  event->Stop();
232 
233  ARMNN_ASSERT(!m_Parents.empty());
234  ARMNN_ASSERT(event == m_Parents.top());
235  m_Parents.pop();
236 
237  Event* parent = m_Parents.empty() ? nullptr : m_Parents.top();
238  IgnoreUnused(parent);
239  ARMNN_ASSERT(event->GetParentEvent() == parent);
240 
241 #if ARMNN_STREAMLINE_ENABLED
242  ANNOTATE_CHANNEL_END(uint32_t(m_Parents.size()));
243 #endif
244 }
245 
246 int CalcLevel(const Event* eventPtr)
247 {
248  int level = 0;
249  while (eventPtr != nullptr)
250  {
251  eventPtr = eventPtr->GetParentEvent();
252  level++;
253  }
254  return level;
255 }
256 
257 void ProfilerImpl::PopulateInferences(std::vector<const Event*>& outInferences, int& outBaseLevel) const
258 {
259  outInferences.reserve(m_EventSequence.size());
260  for (const auto& event : m_EventSequence)
261  {
262  const Event* eventPtrRaw = event.get();
263  if (eventPtrRaw->GetName() == "EnqueueWorkload")
264  {
265  outBaseLevel = (outBaseLevel == -1) ? CalcLevel(eventPtrRaw) : outBaseLevel;
266  outInferences.push_back(eventPtrRaw);
267  }
268  }
269 }
270 
271 void ProfilerImpl::PopulateDescendants(std::map<const Event*, std::vector<const Event*>>& outDescendantsMap) const
272 {
273  for (const auto& event : m_EventSequence)
274  {
275  const Event* eventPtrRaw = event.get();
276  const Event* parent = eventPtrRaw->GetParentEvent();
277 
278  if (!parent)
279  {
280  continue;
281  }
282 
283  auto it = outDescendantsMap.find(parent);
284  if (it == outDescendantsMap.end())
285  {
286  outDescendantsMap.emplace(parent, std::vector<const Event*>({ eventPtrRaw }));
287  }
288  else
289  {
290  it->second.push_back(eventPtrRaw);
291  }
292  }
293 }
294 
296  std::string layerDetailsStr)
297 {
299  detailsObject.SetAndParseDetails(layerDetailsStr);
300 
301 }
302 
303 void ExtractJsonObjects(unsigned int inferenceIndex,
304  const Event* parentEvent,
305  JsonChildObject& parentObject,
306  std::map<const Event*, std::vector<const Event*>> descendantsMap)
307 {
308  ARMNN_ASSERT(parentEvent);
309 
310  // If profiling GUID is entered, process it
311  if (parentEvent->GetProfilingGuid().has_value())
312  {
313  profiling::ProfilingGuid profilingGuid;
314  profilingGuid = parentEvent->GetProfilingGuid().value();
315  parentObject.SetGuid(profilingGuid);
316  }
317  std::vector<Measurement> instrumentMeasurements = parentEvent->GetMeasurements();
318  unsigned int childIdx = 0;
319  for (size_t measurementIndex = 0; measurementIndex < instrumentMeasurements.size(); ++measurementIndex, ++childIdx)
320  {
321  if (inferenceIndex == 0)
322  {
323  // Only add kernel measurement once, in case of multiple inferences
324  JsonChildObject measurementObject{ instrumentMeasurements[measurementIndex].m_Name };
325  measurementObject.SetUnit(instrumentMeasurements[measurementIndex].m_Unit);
326  measurementObject.SetType(JsonObjectType::Measurement);
327 
328  ARMNN_ASSERT(parentObject.NumChildren() == childIdx);
329  parentObject.AddChild(measurementObject);
330  }
331 
332  parentObject.GetChild(childIdx).AddMeasurement(instrumentMeasurements[measurementIndex].m_Value);
333  }
334 
335  auto childEventsIt = descendantsMap.find(parentEvent);
336  if (childEventsIt != descendantsMap.end())
337  {
338  for (auto childEvent : childEventsIt->second)
339  {
340  if (inferenceIndex == 0)
341  {
342  // Only add second level once, in case of multiple inferences
343  JsonChildObject childObject{ childEvent->GetName() };
344  childObject.SetType(JsonObjectType::Event);
345  parentObject.AddChild(childObject);
346  }
347 
348  // Recursively process children. In reality this won't be very deep recursion. ~4-6 levels deep.
349  ExtractJsonObjects(inferenceIndex, childEvent, parentObject.GetChild(childIdx), descendantsMap);
350 
351  childIdx++;
352  }
353  }
354 }
355 
356 void ProfilerImpl::Print(std::ostream& outStream) const
357 {
358  // Makes sure timestamps are output with 6 decimals, and save old settings.
359  std::streamsize oldPrecision = outStream.precision();
360  outStream.precision(6);
361  std::ios_base::fmtflags oldFlags = outStream.flags();
362  outStream.setf(std::ios::fixed);
363  JsonPrinter printer(outStream);
364 
365  // First find all the "inference" Events and print out duration measurements.
366  int baseLevel = -1;
367  std::vector<const Event*> inferences;
368  PopulateInferences(inferences, baseLevel);
369 
370  // Second map out descendants hierarchy
371  std::map<const Event*, std::vector<const Event*>> descendantsMap;
372  PopulateDescendants(descendantsMap);
373 
374  JsonChildObject inferenceObject{ "inference_measurements" };
375  std::vector<JsonChildObject> workloadObjects;
376  std::map<unsigned int, std::vector<JsonChildObject>> workloadToKernelObjects;
377 
378  for (unsigned int inferenceIndex = 0; inferenceIndex < inferences.size(); ++inferenceIndex)
379  {
380  auto inference = inferences[inferenceIndex];
381  ExtractJsonObjects(inferenceIndex, inference, inferenceObject, descendantsMap);
382  }
383 
384  printer.PrintHeader();
385  printer.PrintArmNNHeader();
386 
387  if (m_ProfilingDetails.get()->DetailsExist() && m_EnableDetailsToStdOut)
388  {
389  JsonChildObject detailsObject{ "layer_details" };
390  ConfigureDetailsObject(detailsObject, m_ProfilingDetails.get()->GetProfilingDetails());
391 
392  size_t id = 0;
393  printer.PrintJsonChildObject(detailsObject, id);
394  }
395 
396  // print inference object, also prints child layer and kernel measurements
397  size_t id = 0;
398  printer.PrintJsonChildObject(inferenceObject, id);
399 
400  // end of ArmNN
401  printer.PrintNewLine();
402  printer.PrintFooter();
403 
404  // end of main JSON object
405  printer.PrintNewLine();
406  printer.PrintFooter();
407  printer.PrintNewLine();
408 
409  // Restores previous precision settings.
410  outStream.flags(oldFlags);
411  outStream.precision(oldPrecision);
412 }
413 
414 void ProfilerImpl::AnalyzeEventsAndWriteResults(std::ostream& outStream) const
415 {
416  // Stack should be empty now.
417  const bool saneMarkerSequence = m_Parents.empty();
418 
419  // Abort if the sequence of markers was found to have incorrect information:
420  // The stats cannot be trusted.
421  if (!saneMarkerSequence)
422  {
423  outStream << "Cannot write profiling stats. "
424  "Unexpected errors were found when analyzing the sequence of logged events, "
425  "which may lead to plainly wrong stats. The profiling system may contain implementation "
426  "issues or could have been used in an unsafe manner." << std::endl;
427  return;
428  }
429 
430  // Analyzes the full sequence of events.
432  m_EventSequence.cend(),
433  outStream);
434 
435  // Aggregates events by tag if requested (spams the output stream if done for all tags).
436  if (g_AggregateProfilingEventsByInference)
437  {
438  outStream << std::endl;
439  outStream << "***" << std::endl;
440  outStream << "*** Per Inference Stats" << std::endl;
441  outStream << "***" << std::endl;
442  outStream << std::endl;
443 
444  int baseLevel = -1;
445  std::vector<const Event*> inferences;
446  PopulateInferences(inferences, baseLevel);
447 
448  // Second map out descendants hierarchy
449  std::map<const Event*, std::vector<const Event*>> descendantsMap;
450  PopulateDescendants(descendantsMap);
451 
452  std::function<void(const Event*, std::vector<const Event*>&)>
453  FindDescendantEvents = [&](const Event* eventPtr, std::vector<const Event*>& sequence)
454  {
455  sequence.push_back(eventPtr);
456 
457  if (CalcLevel(eventPtr) > baseLevel+2) //We only care about levels as deep as workload executions.
458  {
459  return;
460  }
461 
462  auto children = descendantsMap.find(eventPtr);
463  if (children == descendantsMap.end())
464  {
465  return;
466  }
467 
468  if (!(children->second.empty()))
469  {
470  return FindDescendantEvents(children->second[0], sequence);
471  }
472  };
473 
474  // Third, find events belonging to each inference
475  int inferenceIdx = 0;
476  for (auto inference : inferences)
477  {
478  std::vector<const Event*> sequence;
479 
480  //build sequence, depth first
481  FindDescendantEvents(inference, sequence);
482 
483  outStream << "> Begin Inference: " << inferenceIdx << std::endl;
484  outStream << std::endl;
485  AnalyzeEventSequenceAndWriteResults(sequence.cbegin(),
486  sequence.cend(),
487  outStream);
488  outStream << std::endl;
489  outStream << "> End Inference: " << inferenceIdx << std::endl;
490 
491  inferenceIdx++;
492  }
493  }
494 }
495 
496 std::uint32_t ProfilerImpl::GetEventColor(const BackendId& backendId) const
497 {
498  static BackendId cpuRef("CpuRef");
499  static BackendId cpuAcc("CpuAcc");
500  static BackendId gpuAcc("GpuAcc");
501  if (backendId == cpuRef)
502  {
503  // Cyan
504  return 0xffff001b;
505  }
506  else if (backendId == cpuAcc)
507  {
508  // Green
509  return 0x00ff001b;
510  }
511  else if (backendId == gpuAcc)
512  {
513  // Purple
514  return 0xff007f1b;
515  }
516  else
517  {
518  // Dark gray
519  return 0x5555551b;
520  }
521 }
522 
523 // The thread_local pointer to the profiler instance.
524 thread_local IProfiler* tl_Profiler = nullptr;
525 
527 {
528  // Global reference to the single ProfileManager instance allowed.
529  static ProfilerManager s_ProfilerManager;
530  return s_ProfilerManager;
531 }
532 
534 {
535  tl_Profiler = profiler;
536 }
537 
539 {
540  return tl_Profiler;
541 }
542 
543 void IProfiler::EnableProfiling(bool enableProfiling)
544 {
545  pProfilerImpl->EnableProfiling(enableProfiling);
546 }
547 
549 {
550  pProfilerImpl->EnableNetworkDetailsToStdOut();
551 }
552 
554 {
555  return pProfilerImpl->IsProfilingEnabled();
556 }
557 
558 void IProfiler::AnalyzeEventsAndWriteResults(std::ostream& outStream) const
559 {
560  pProfilerImpl->AnalyzeEventsAndWriteResults(outStream);
561 }
562 
563 void IProfiler::Print(std::ostream& outStream) const
564 {
565  pProfilerImpl->Print(outStream);
566 }
567 
568 Event* IProfiler::BeginEvent(const BackendId& backendId,
569  const std::string& label,
570  std::vector<InstrumentPtr>&& instruments,
572 {
573  return pProfilerImpl->BeginEvent(this, backendId, label, std::move(instruments), guid);
574 }
575 
576 IProfiler::~IProfiler() = default;
577 IProfiler::IProfiler() : pProfilerImpl(new ProfilerImpl())
578 {};
579 
580 } // namespace armnn
JsonChildObject & GetChild(const unsigned int index)
Definition: JsonPrinter.hpp:63
Event class records measurements reported by BeginEvent()/EndEvent() and returns measurements when Ev...
const Event * GetEventPtr(const Event *ptr)
Definition: Profiling.cpp:109
constexpr bool g_WriteReportToStdOutOnProfilerDestruction
Definition: Profiling.cpp:41
void AddChild(const JsonChildObject &childObject)
Definition: JsonPrinter.hpp:58
int CalcLevel(const Event *eventPtr)
Definition: Profiling.cpp:246
static ProfilerManager & GetInstance()
Definition: Profiling.cpp:526
Measurement FindMeasurement(const std::string &name, const Event *event)
Definition: Profiling.cpp:43
void PrintHeader()
Definition: JsonUtils.hpp:58
uint32_t GetEventColor(const BackendId &backendId) const
Definition: Profiling.cpp:496
std::vector< Measurement > FindKernelMeasurements(const Event *event)
Definition: Profiling.cpp:62
void Print(std::ostream &outStream) const
Print stats for events in JSON Format to the given output stream.
Definition: Profiling.cpp:563
std::map< std::string, ProfilingEventStats > CalculateProfilingEventStats() const
Definition: Profiling.cpp:82
void AnalyzeEventsAndWriteResults(std::ostream &outStream) const
Analyzes the tracked events and writes the results to the given output stream.
Definition: Profiling.cpp:558
const std::string & GetName() const
Get the name of the event.
Copyright (c) 2021 ARM Limited and Contributors.
void IgnoreUnused(Ts &&...)
Optional< profiling::ProfilingGuid > GetProfilingGuid() const
Get the associated profiling GUID if the event is a workload.
void PrintArmNNHeader()
Definition: JsonUtils.hpp:64
void AnalyzeEventSequenceAndWriteResults(EventIterType first, EventIterType last, std::ostream &outStream) const
void EnableNetworkDetailsToStdOut()
Print out details of each layer within the network that possesses a descriptor.
Definition: Profiling.cpp:548
void SetAndParseDetails(std::string layerDetailsStr)
Definition: JsonPrinter.hpp:43
size_t NumChildren() const
Definition: JsonPrinter.hpp:73
IProfiler * GetProfiler()
Definition: Profiling.cpp:538
void ConfigureDetailsObject(JsonChildObject &detailsObject, std::string layerDetailsStr)
Definition: Profiling.cpp:295
BackendId GetBackendId() const
Get the backend id of the event.
void AnalyzeEventsAndWriteResults(std::ostream &outStream) const
Definition: Profiling.cpp:414
void EnableNetworkDetailsToStdOut()
Definition: Profiling.cpp:200
const std::vector< Measurement > GetMeasurements() const
Get the recorded measurements calculated between Start() and Stop()
void ExtractJsonObjects(unsigned int inferenceIndex, const Event *parentEvent, JsonChildObject &parentObject, std::map< const Event *, std::vector< const Event *>> descendantsMap)
Definition: Profiling.cpp:303
void PrintFooter()
Definition: JsonUtils.hpp:51
void EnableProfiling(bool enableProfiling)
Enables/disables profiling for this profiler.
Definition: Profiling.cpp:543
static const std::string WALL_CLOCK_TIME_STOP
void SetType(JsonObjectType type)
Definition: JsonPrinter.hpp:78
static const std::string WALL_CLOCK_TIME_START
constexpr std::size_t g_ProfilingEventCountHint
Definition: Profiling.cpp:29
bool has_value() const noexcept
Definition: Optional.hpp:53
static const std::string WALL_CLOCK_TIME
bool IsProfilingEnabled()
Checks whether profiling is enabled.
Definition: Profiling.cpp:553
void AddMeasurement(const double measurement)
Definition: JsonPrinter.hpp:38
#define ARMNN_ASSERT(COND)
Definition: Assert.hpp:14
void EndEvent(Event *event)
Definition: Profiling.cpp:229
void PrintJsonChildObject(const JsonChildObject &object, size_t &id)
Definition: JsonPrinter.cpp:15
constexpr bool g_WriteProfilingEventSequence
Definition: Profiling.cpp:32
constexpr bool g_AggregateProfilingEventsByInference
Definition: Profiling.cpp:37
void PrintNewLine()
Definition: JsonUtils.hpp:46
void Print(std::ostream &outStream) const
Definition: Profiling.cpp:356
void RegisterProfiler(IProfiler *profiler)
Definition: Profiling.cpp:533
DescPtr m_ProfilingDetails
Definition: Profiling.hpp:103
const std::string & Get() const
Definition: BackendId.hpp:136
void SetUnit(const Measurement::Unit unit)
Definition: JsonPrinter.hpp:68
void SetGuid(profiling::ProfilingGuid guid)
Definition: JsonPrinter.hpp:53
void PopulateInferences(std::vector< const Event *> &outInferences, int &outBaseLevel) const
Definition: Profiling.cpp:257
std::stack< Event * > m_Parents
Definition: Profiling.hpp:101
const Event * GetParentEvent() const
Get the pointer of the parent event.
thread_local IProfiler * tl_Profiler
Definition: Profiling.cpp:524
void PopulateDescendants(std::map< const Event *, std::vector< const Event *>> &outDescendantsMap) const
Definition: Profiling.cpp:271
Event * BeginEvent(armnn::IProfiler *profiler, const BackendId &backendId, const std::string &name, std::vector< InstrumentPtr > &&instruments, const Optional< profiling::ProfilingGuid > &guid)
Definition: Profiling.cpp:205
std::vector< EventPtr > m_EventSequence
Definition: Profiling.hpp:102
void EnableProfiling(bool enableProfiling)
Definition: Profiling.cpp:195